diff --git a/.betterer.results b/.betterer.results index c90b157dd4a..186b3a43f37 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5,329 +5,329 @@ // exports[`no enzyme tests`] = { value: `{ - "packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx:2315765050": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx:4199795290": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx:1355456933": [ - [1, 31, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx:3311646309": [ + [0, 31, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx:3475964456": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx:4145620610": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/FormField/FormField.test.tsx:2387359174": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/FormField/FormField.test.tsx:3429087660": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx:4019574269": [ - [2, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx:3129955645": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Gauge/Gauge.test.tsx:1435004725": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Gauge/Gauge.test.tsx:2525939157": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Graph/Graph.test.tsx:2882401917": [ - [1, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Graph/Graph.test.tsx:1664091255": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.test.tsx:3734531523": [ - [1, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.test.tsx:1865444105": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/List/AbstractList.test.tsx:2210654375": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/List/AbstractList.test.tsx:2972876749": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Logs/LogLabels.test.tsx:1113070745": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Logs/LogLabels.test.tsx:1029448019": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Logs/LogMessageAnsi.test.tsx:1478948568": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Logs/LogMessageAnsi.test.tsx:1630730648": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx:636540759": [ - [3, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx:2719724375": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Logs/LogRows.test.tsx:2288254498": [ - [3, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Logs/LogRows.test.tsx:3121815234": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:1297745712": [ - [1, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:375894800": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Slider/Slider.test.tsx:2110443485": [ - [3, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Slider/Slider.test.tsx:751112695": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Typeahead/PartialHighlighter.test.tsx:3831493850": [ - [1, 31, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Typeahead/PartialHighlighter.test.tsx:1751923376": [ + [0, 31, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx:1026793168": [ - [1, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx:972524250": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.test.tsx:3968135869": [ - [1, 17, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.test.tsx:3512289373": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/slate-plugins/braces.test.tsx:2551821825": [ - [3, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/slate-plugins/braces.test.tsx:1440546721": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/slate-plugins/clear.test.tsx:4145618392": [ - [3, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/slate-plugins/clear.test.tsx:1085648664": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/slate-plugins/runner.test.tsx:2165687770": [ - [3, 19, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/slate-plugins/runner.test.tsx:446043290": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "packages/grafana-ui/src/slate-plugins/suggestions.test.tsx:4138775391": [ - [1, 18, 13, "RegExp match", "2409514259"] + "packages/grafana-ui/src/slate-plugins/suggestions.test.tsx:3654981205": [ + [0, 18, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.test.js:3515574241": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.test.js:1837731860": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.test.js:1200884350": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.test.js:3094256278": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js:2643658696": [ + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.test.js:1974748555": [ [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js:4118842298": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js:3669774385": [ - [15, 26, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js:2095199604": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:793239444": [ - [15, 26, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js:2288177454": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js:2127169675": [ - [15, 17, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js:814916029": [ - [15, 26, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js:2200354834": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js:3242453659": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:1301875390": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js:2881451220": [ - [15, 17, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:2159985979": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js:3713828679": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js:3788127934": [ - [17, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js:1119974018": [ - [15, 19, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js:232429058": [ + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.test.js:940336852": [ [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.test.js:2626977087": [ - [15, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.test.js:4256741694": [ + [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js:954294244": [ - [15, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.test.js:2931161174": [ + [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js:208811948": [ - [15, 17, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js:1676554632": [ + [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js:1197952226": [ - [15, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js:186764954": [ + [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js:3714128222": [ - [15, 17, 13, "RegExp match", "2409514259"] - ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js:2249890922": [ + "packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js:3242042907": [ [14, 26, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js:4107025050": [ + "packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js:2807329716": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:1734982398": [ + [14, 26, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js:3807792910": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js:1478502145": [ + [14, 17, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js:3826510429": [ + [14, 26, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js:2408389970": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js:3960703835": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js:2429764318": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js:1966455998": [ + [14, 17, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js:3813002651": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js:3006381933": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js:3097530078": [ + [16, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js:2623922632": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js:1117377794": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.test.js:743308415": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js:4018342820": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js:2286101708": [ + [15, 17, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js:2106409544": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js:1423129438": [ + [15, 17, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js:2326471104": [ + [13, 26, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js:381298544": [ + [14, 19, 13, "RegExp match", "2409514259"] + ], + "packages/jaeger-ui-components/src/common/CopyIcon.test.js:187212136": [ [15, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/common/CopyIcon.test.js:3119664776": [ - [15, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/common/NewWindowIcon.test.js:1750458349": [ + [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/common/NewWindowIcon.test.js:3878561197": [ - [15, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/common/UiFindInput.test.js:2207118020": [ + [14, 19, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/common/UiFindInput.test.js:2258975438": [ - [15, 19, 13, "RegExp match", "2409514259"] + "packages/jaeger-ui-components/src/url/ReferenceLink.test.js:3249503373": [ + [14, 26, 13, "RegExp match", "2409514259"] ], - "packages/jaeger-ui-components/src/url/ReferenceLink.test.js:830986413": [ - [15, 26, 13, "RegExp match", "2409514259"] + "public/app/core/components/PageActionBar/PageActionBar.test.tsx:1251504193": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/core/components/PageActionBar/PageActionBar.test.tsx:2111991019": [ + "public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx:3032694716": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx:2026575657": [ + [0, 26, 13, "RegExp match", "2409514259"] + ], + "public/app/core/components/Select/FolderPicker.test.tsx:993468764": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/core/components/Select/MetricSelect.test.tsx:3351544014": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/alerting/AlertRuleList.test.tsx:2998938420": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/alerting/TestRuleResult.test.tsx:2358420489": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/api-keys/ApiKeysAddedModal.test.tsx:3246264379": [ + [0, 20, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx:1463123173": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx:1969004590": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx:948029434": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:2357087833": [ + [0, 35, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx:2723773538": [ + [0, 35, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.test.tsx:2851646279": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/datasources/DashboardsTable.test.tsx:1950355032": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/datasources/DataSourceDashboards.test.tsx:1369048021": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/datasources/DataSourcesListPage.test.tsx:2960099202": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/datasources/settings/BasicSettings.test.tsx:625016324": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/datasources/settings/ButtonRow.test.tsx:1422996720": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx:4164297658": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/explore/LiveLogs.test.tsx:156663779": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx:3328200031": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/explore/RunButton.test.tsx:4267530266": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/explore/SecondaryActions.test.tsx:1177396128": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/explore/TimeSyncButton.test.tsx:853739820": [ + [0, 17, 13, "RegExp match", "2409514259"] + ], + "public/app/features/folders/FolderSettingsPage.test.tsx:1109052730": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/invites/InviteesTable.test.tsx:3612992381": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/org/OrgDetailsPage.test.tsx:3835042085": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/org/OrgProfile.test.tsx:623809345": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/CreateTeam.test.tsx:1750035593": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/TeamGroupSync.test.tsx:2526985933": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/TeamList.test.tsx:854193970": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/TeamMemberRow.test.tsx:1649328210": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/TeamMembers.test.tsx:4089428239": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/TeamPages.test.tsx:3990420214": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/teams/TeamSettings.test.tsx:2043271249": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/users/UsersActionBar.test.tsx:3740177621": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/users/UsersListPage.test.tsx:3908145117": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/features/users/UsersTable.test.tsx:263958312": [ + [0, 19, 13, "RegExp match", "2409514259"] + ], + "public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx:3822406835": [ [1, 19, 13, "RegExp match", "2409514259"] ], - "public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx:1284778262": [ - [2, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:1224072551": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx:4115956163": [ - [2, 26, 13, "RegExp match", "2409514259"] - ], - "public/app/core/components/Select/FolderPicker.test.tsx:3389713532": [ + "public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:2097436158": [ [1, 19, 13, "RegExp match", "2409514259"] ], - "public/app/core/components/Select/MetricSelect.test.tsx:3409251428": [ - [1, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx:3481855642": [ + [0, 26, 13, "RegExp match", "2409514259"] ], - "public/app/features/alerting/AlertRuleList.test.tsx:1800339390": [ - [1, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx:248699332": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "public/app/features/alerting/TestRuleResult.test.tsx:4118445705": [ - [3, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.test.tsx:1107283234": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/features/api-keys/ApiKeysAddedModal.test.tsx:3516294961": [ - [1, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx:3456478975": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx:2934056165": [ - [1, 17, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx:1488067923": [ + [0, 26, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx:2536713486": [ - [1, 17, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx:146069464": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx:1262111696": [ - [1, 17, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx:3658171175": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx:809006195": [ - [1, 35, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx:1551927716": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx:3682134210": [ - [1, 35, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx:3570129984": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.test.tsx:3293585799": [ - [1, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx:2402631398": [ + [0, 17, 13, "RegExp match", "2409514259"] ], - "public/app/features/datasources/DashboardsTable.test.tsx:1799076152": [ - [1, 19, 13, "RegExp match", "2409514259"] + "public/app/plugins/datasource/prometheus/configuration/AzureCredentialsForm.test.tsx:3424320489": [ + [0, 19, 13, "RegExp match", "2409514259"] ], - "public/app/features/datasources/DataSourceDashboards.test.tsx:3705773301": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/datasources/DataSourcesListPage.test.tsx:1470540840": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/datasources/settings/BasicSettings.test.tsx:3867031406": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/datasources/settings/ButtonRow.test.tsx:1546608090": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx:2556927610": [ - [1, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/features/explore/LiveLogs.test.tsx:1667605379": [ - [2, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx:492930613": [ - [1, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/features/explore/RunButton.test.tsx:138299098": [ - [3, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/explore/SecondaryActions.test.tsx:2096700362": [ - [2, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/explore/TimeSyncButton.test.tsx:4230066214": [ - [2, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/features/folders/FolderSettingsPage.test.tsx:3884290298": [ - [2, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/invites/InviteesTable.test.tsx:3077684439": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/org/OrgDetailsPage.test.tsx:2540662821": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/org/OrgProfile.test.tsx:4200739179": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/CreateTeam.test.tsx:262744099": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/TeamGroupSync.test.tsx:1549471437": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/TeamList.test.tsx:3009556824": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/TeamMemberRow.test.tsx:7032440": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/TeamMembers.test.tsx:3993436101": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/TeamPages.test.tsx:1265047708": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/teams/TeamSettings.test.tsx:2628968507": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/users/UsersActionBar.test.tsx:4031641375": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/users/UsersListPage.test.tsx:2626906707": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/features/users/UsersTable.test.tsx:3051231816": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx:2813729913": [ - [2, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:2974837543": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:3888529428": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx:1089831034": [ - [1, 26, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx:2916632804": [ - [1, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.test.tsx:1635510338": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx:767000341": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx:2608233977": [ - [1, 26, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx:2337402520": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx:1661240493": [ - [1, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx:2909181412": [ - [1, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx:1527527456": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx:2923116422": [ - [1, 17, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/datasource/prometheus/configuration/AzureCredentialsForm.test.tsx:1231427": [ - [1, 19, 13, "RegExp match", "2409514259"] - ], - "public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx:1527575498": [ - [1, 31, 13, "RegExp match", "2409514259"] + "public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx:1597135392": [ + [0, 31, 13, "RegExp match", "2409514259"] ] }` }; diff --git a/.drone.yml b/.drone.yml index b28736013e1..249df7599f2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -23,17 +23,17 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl verify-drone @@ -51,13 +51,13 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss @@ -65,7 +65,7 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend - commands: - yarn run prettier:check @@ -76,19 +76,19 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -96,7 +96,7 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-frontend trigger: event: @@ -126,7 +126,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -134,17 +134,17 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -152,7 +152,7 @@ steps: depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -161,7 +161,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -170,7 +170,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss @@ -178,13 +178,13 @@ steps: - gen-version - yarn-install environment: null - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -197,7 +197,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: ensure-cuetsified - commands: - . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss @@ -208,7 +208,7 @@ steps: - build-frontend - build-frontend-packages environment: null - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package - commands: - ./scripts/grafana-server/start-server @@ -221,7 +221,7 @@ steps: environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: grafana-server - commands: - apt-get install -y netcat @@ -299,7 +299,7 @@ steps: - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-storybook - commands: - yarn wait-on http://$HOST:$PORT @@ -319,7 +319,7 @@ steps: - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss -archs amd64 @@ -377,7 +377,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -398,7 +398,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: postgres-integration-tests - commands: - apt-get update @@ -413,7 +413,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: mysql-integration-tests trigger: event: @@ -445,7 +445,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -457,13 +457,13 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - |- @@ -475,7 +475,7 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - yarn run prettier:checkDocs @@ -483,7 +483,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-docs - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -492,13 +492,13 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./scripts/ci-reference-docs-lint.sh ci depends_on: - build-frontend-packages - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-docs - commands: - mkdir -p /hugo/content/docs/grafana @@ -533,7 +533,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -545,13 +545,13 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - |- @@ -563,7 +563,7 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - yarn run prettier:checkDocs @@ -571,7 +571,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-docs - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -580,13 +580,13 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./scripts/ci-reference-docs-lint.sh ci depends_on: - build-frontend-packages - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-docs - commands: - mkdir -p /hugo/content/docs/grafana @@ -622,7 +622,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -630,17 +630,17 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl verify-drone @@ -658,13 +658,13 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss @@ -672,7 +672,7 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend - commands: - yarn run prettier:check @@ -683,19 +683,19 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -703,7 +703,7 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-frontend trigger: branch: main @@ -731,7 +731,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -739,17 +739,17 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" @@ -770,7 +770,7 @@ steps: from_secret: github_token TEST_TAG: v0.0.0-test failure: ignore - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: trigger-test-release when: paths: @@ -794,7 +794,7 @@ steps: depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -803,7 +803,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -812,7 +812,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin @@ -822,13 +822,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -841,7 +841,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: ensure-cuetsified - commands: - ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --sign @@ -859,7 +859,7 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package - commands: - ./scripts/grafana-server/start-server @@ -872,7 +872,7 @@ steps: environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: grafana-server - commands: - apt-get install -y netcat @@ -950,7 +950,7 @@ steps: - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-storybook - commands: - ./bin/grabpl store-storybook --deployment canary --src-bucket grafana-storybook @@ -991,7 +991,7 @@ steps: GRAFANA_MISC_STATS_API_KEY: from_secret: grafana_misc_stats_api_key failure: ignore - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: publish-frontend-metrics when: repo: @@ -1001,7 +1001,7 @@ steps: - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss @@ -1079,7 +1079,7 @@ steps: environment: NPM_TOKEN: from_secret: npm_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: release-canary-npm-packages when: repo: @@ -1163,7 +1163,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1184,7 +1184,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: postgres-integration-tests - commands: - apt-get update @@ -1199,7 +1199,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: mysql-integration-tests trigger: branch: main @@ -1235,7 +1235,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1322,7 +1322,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1406,7 +1406,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1414,24 +1414,24 @@ steps: - ./bin/grabpl gen-version ${DRONE_TAG} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss ${DRONE_TAG} depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss ${DRONE_TAG} @@ -1440,7 +1440,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss ${DRONE_TAG} @@ -1449,7 +1449,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin @@ -1459,13 +1459,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -1478,7 +1478,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: ensure-cuetsified - commands: - ./bin/grabpl package --jobs 8 --edition oss --sign ${DRONE_TAG} @@ -1496,14 +1496,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss --shouldSave @@ -1540,7 +1540,7 @@ steps: environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: grafana-server - commands: - apt-get install -y netcat @@ -1618,7 +1618,7 @@ steps: - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-storybook - commands: - ./bin/grabpl upload-cdn --edition oss --src-bucket "$${PRERELEASE_BUCKET}" --src-dir @@ -1712,7 +1712,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1720,23 +1720,23 @@ steps: - ./bin/grabpl gen-version ${DRONE_TAG} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl shellcheck depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: shellcheck - commands: - |- @@ -1748,7 +1748,7 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - ./bin/grabpl lint-backend --edition oss @@ -1756,7 +1756,7 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend - commands: - yarn run prettier:check @@ -1767,19 +1767,19 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -1787,7 +1787,7 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-frontend trigger: event: @@ -1836,7 +1836,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1857,7 +1857,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: postgres-integration-tests - commands: - apt-get update @@ -1872,7 +1872,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: mysql-integration-tests trigger: event: @@ -1913,7 +1913,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1972,7 +1972,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1987,7 +1987,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -2003,32 +2003,32 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: init-enterprise - commands: - make gen-go depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl gen-version ${DRONE_TAG} depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise ${DRONE_TAG} depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition enterprise ${DRONE_TAG} @@ -2037,7 +2037,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition enterprise ${DRONE_TAG} @@ -2046,7 +2046,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --sign --signing-admin @@ -2056,13 +2056,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -2075,14 +2075,14 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: ensure-cuetsified - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 ${DRONE_TAG} depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend-enterprise2 - commands: - ./bin/grabpl package --jobs 8 --edition enterprise --sign ${DRONE_TAG} @@ -2101,14 +2101,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition enterprise --shouldSave @@ -2146,7 +2146,7 @@ steps: ARCH: linux-amd64 PORT: 3001 RUNDIR: scripts/grafana-server/tmp-grafana-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: grafana-server - commands: - apt-get install -y netcat @@ -2267,7 +2267,7 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package-enterprise2 - commands: - ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "$${PRERELEASE_BUCKET}" @@ -2329,7 +2329,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2344,7 +2344,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -2360,25 +2360,25 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: init-enterprise - commands: - make gen-go depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl gen-version ${DRONE_TAG} depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - |- @@ -2390,7 +2390,7 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - ./bin/grabpl lint-backend --edition enterprise @@ -2398,7 +2398,7 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend - commands: - yarn run prettier:check @@ -2409,19 +2409,19 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition enterprise depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend - commands: - ./bin/grabpl integration-tests --edition enterprise depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -2429,7 +2429,7 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-frontend - commands: - ./bin/grabpl lint-backend --edition enterprise2 @@ -2437,19 +2437,19 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend-enterprise2 - commands: - ./bin/grabpl test-backend --edition enterprise2 depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-enterprise2 - commands: - ./bin/grabpl integration-tests --edition enterprise2 depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration-enterprise2 trigger: event: @@ -2508,7 +2508,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2523,7 +2523,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -2539,7 +2539,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: init-enterprise - commands: - apt-get update @@ -2554,7 +2554,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: postgres-integration-tests - commands: - apt-get update @@ -2569,7 +2569,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: mysql-integration-tests - commands: - dockerize -wait tcp://redis:6379/0 -timeout 120s @@ -2578,7 +2578,7 @@ steps: - init-enterprise environment: REDIS_URL: redis://redis:6379/0 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2587,7 +2587,7 @@ steps: - init-enterprise environment: MEMCACHED_HOSTS: memcached:11211 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: memcached-integration-tests trigger: event: @@ -2632,7 +2632,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2707,7 +2707,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2785,7 +2785,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2846,7 +2846,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2925,7 +2925,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2987,7 +2987,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3025,7 +3025,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3063,10 +3063,16 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.4 + name: yarn-install - commands: - ./bin/grabpl artifacts npm retrieve --tag v${TAG} depends_on: @@ -3085,7 +3091,7 @@ steps: environment: NPM_TOKEN: from_secret: npm_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: release-npm-packages trigger: event: @@ -3113,7 +3119,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3180,7 +3186,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3188,24 +3194,24 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -3214,7 +3220,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} @@ -3223,7 +3229,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin @@ -3233,13 +3239,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -3252,7 +3258,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: ensure-cuetsified - commands: - ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --sign @@ -3270,14 +3276,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition oss --shouldSave @@ -3314,7 +3320,7 @@ steps: environment: ARCH: linux-amd64 PORT: 3001 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: grafana-server - commands: - apt-get install -y netcat @@ -3392,7 +3398,7 @@ steps: - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-storybook - commands: - ./bin/grabpl upload-cdn --edition oss --src-bucket "grafana-static-assets" @@ -3456,7 +3462,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3464,23 +3470,23 @@ steps: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - make gen-go - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl shellcheck depends_on: - grabpl - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: shellcheck - commands: - |- @@ -3492,7 +3498,7 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - ./bin/grabpl lint-backend --edition oss @@ -3500,7 +3506,7 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend - commands: - yarn run prettier:check @@ -3511,19 +3517,19 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -3531,7 +3537,7 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-frontend trigger: ref: @@ -3574,7 +3580,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3595,7 +3601,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: postgres-integration-tests - commands: - apt-get update @@ -3610,7 +3616,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: mysql-integration-tests trigger: ref: @@ -3645,7 +3651,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -3693,7 +3699,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3708,7 +3714,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -3721,32 +3727,32 @@ steps: depends_on: - clone-enterprise environment: {} - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: init-enterprise - commands: - make gen-go depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} @@ -3755,7 +3761,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition enterprise --build-id @@ -3765,7 +3771,7 @@ steps: - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-frontend-packages - commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --sign --signing-admin @@ -3775,13 +3781,13 @@ steps: environment: GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-plugins - commands: - ./bin/linux-amd64/grafana-cli cue validate-schema --grafana-root . depends_on: - build-backend - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: validate-scuemata - commands: - '# It is required that the generated Typescript be in sync with the input CUE @@ -3794,7 +3800,7 @@ steps: - ./bin/linux-amd64/grafana-cli cue gen-ts --grafana-root . --diff depends_on: - validate-scuemata - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: ensure-cuetsified - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER} @@ -3802,7 +3808,7 @@ steps: depends_on: - gen-version - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-backend-enterprise2 - commands: - ./bin/grabpl package --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} @@ -3822,14 +3828,14 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package - commands: - ls dist/*.tar.gz* - cp dist/*.tar.gz* packaging/docker/ depends_on: - package - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: copy-packages-for-docker - commands: - ./bin/grabpl build-docker --edition enterprise --shouldSave @@ -3867,7 +3873,7 @@ steps: ARCH: linux-amd64 PORT: 3001 RUNDIR: scripts/grafana-server/tmp-grafana-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: grafana-server - commands: - apt-get install -y netcat @@ -3945,7 +3951,7 @@ steps: - build-frontend-packages environment: NODE_OPTIONS: --max_old_space_size=4096 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: build-storybook - commands: - ./bin/grabpl upload-cdn --edition enterprise --src-bucket "grafana-static-assets" @@ -3993,7 +3999,7 @@ steps: from_secret: gpg_pub_key GRAFANA_API_KEY: from_secret: grafana_api_key - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: package-enterprise2 - commands: - ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "grafana-static-assets" @@ -4048,7 +4054,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4063,7 +4069,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -4076,25 +4082,25 @@ steps: depends_on: - clone-enterprise environment: {} - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: init-enterprise - commands: - make gen-go depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: wire-install - commands: - yarn install --immutable depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: yarn-install - commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} depends_on: - init-enterprise - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: gen-version - commands: - |- @@ -4106,7 +4112,7 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: codespell - commands: - ./bin/grabpl lint-backend --edition enterprise @@ -4114,7 +4120,7 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend - commands: - yarn run prettier:check @@ -4125,19 +4131,19 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-frontend - commands: - ./bin/grabpl test-backend --edition enterprise depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend - commands: - ./bin/grabpl integration-tests --edition enterprise depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration - commands: - yarn run ci:test-frontend @@ -4145,7 +4151,7 @@ steps: - yarn-install environment: TEST_MAX_WORKERS: 50% - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-frontend - commands: - ./bin/grabpl lint-backend --edition enterprise2 @@ -4153,19 +4159,19 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: lint-backend-enterprise2 - commands: - ./bin/grabpl test-backend --edition enterprise2 depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-enterprise2 - commands: - ./bin/grabpl integration-tests --edition enterprise2 depends_on: - wire-install - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: test-backend-integration-enterprise2 trigger: ref: @@ -4218,7 +4224,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4233,7 +4239,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: clone-enterprise - commands: - mv bin/grabpl /tmp/ @@ -4246,7 +4252,7 @@ steps: depends_on: - clone-enterprise environment: {} - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: init-enterprise - commands: - apt-get update @@ -4261,7 +4267,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: postgres-integration-tests - commands: - apt-get update @@ -4276,7 +4282,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: mysql-integration-tests - commands: - dockerize -wait tcp://redis:6379/0 -timeout 120s @@ -4285,7 +4291,7 @@ steps: - init-enterprise environment: REDIS_URL: redis://redis:6379/0 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -4294,7 +4300,7 @@ steps: - init-enterprise environment: MEMCACHED_HOSTS: memcached:11211 - image: grafana/build-container:1.5.3 + image: grafana/build-container:1.5.4 name: memcached-integration-tests trigger: ref: @@ -4333,7 +4339,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.41/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -4530,6 +4536,6 @@ kind: secret name: gcp_upload_artifacts_key --- kind: signature -hmac: 803ec2a67b1758ce82ddda8ba1fb276fd23e4cfbfc3c5736bf2fa9e98edc2e6b +hmac: 9380beb722ab1276e9219ae41fa07c84db1e7dc249f69e42386f868c026e1795 ... diff --git a/.eslintignore b/.eslintignore index 1cafcb9a2f0..60181e855a4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -20,3 +20,6 @@ vendor # Auto-generated localisation files public/locales/_build/ public/locales/**/*.js + +# Auto-generated icon file +packages/grafana-ui/src/components/Icon/iconBundle.ts diff --git a/.eslintrc b/.eslintrc index 6c02955b708..107d97eb655 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,12 +1,24 @@ { "extends": ["@grafana/eslint-config"], "root": true, - "plugins": ["@emotion", "lodash", "jest"], + "plugins": ["@emotion", "lodash", "jest", "import"], + "settings": { + "import/internal-regex": "^(app/)|(@grafana)", + "import/external-module-folders": ["node_modules", ".yarn"] + }, "rules": { "react/prop-types": "off", "@emotion/jsx-import": "error", "lodash/import-scope": [2, "member"], - "jest/no-focused-tests": "error" + "jest/no-focused-tests": "error", + "import/order": [ + "error", + { + "groups": [["builtin", "external"], "internal", "parent", "sibling", "index"], + "newlines-between": "always", + "alphabetize": { "order": "asc" } + } + ] }, "overrides": [ { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fe6e7f3b57c..b421e6f3663 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -59,6 +59,7 @@ go.sum @grafana/backend-platform /pkg/services/searchV2/ @grafana/grafana-edge-squad /pkg/services/store/ @grafana/grafana-edge-squad /pkg/infra/filestore/ @grafana/grafana-edge-squad +pkg/tsdb/testdatasource/sims/ @grafana/grafana-edge-squad # Alerting /pkg/services/ngalert @grafana/alerting-squad-backend diff --git a/CHANGELOG.md b/CHANGELOG.md index 508cabd500c..a9738006ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,81 @@ + + +# 8.5.0 (2022-04-21) + +### Features and enhancements + +- **Alerting:** Add contact points provisioning API. [#47197](https://github.com/grafana/grafana/pull/47197), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) +- **Alerting:** Add resolved count to notification title when both firing and resolved present. [#46697](https://github.com/grafana/grafana/pull/46697), [@JacobsonMT](https://github.com/JacobsonMT) +- **Alerting:** Alert rule should wait For duration when execution error state is Alerting. [#47052](https://github.com/grafana/grafana/pull/47052), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Alerting:** Classic conditions can now display multiple values. [#46971](https://github.com/grafana/grafana/pull/46971), [@gotjosh](https://github.com/gotjosh) +- **Alerting:** Display query from grafana-managed alert rules on `/api/v1/rules`. [#45969](https://github.com/grafana/grafana/pull/45969), [@gotjosh](https://github.com/gotjosh) +- **Alerting:** Enhance support for arbitrary group names in managed alerts. [#47785](https://github.com/grafana/grafana/pull/47785), [@gillesdemey](https://github.com/gillesdemey) +- **Alerting:** add field for custom slack endpoint. [#45751](https://github.com/grafana/grafana/pull/45751), [@nathanrodman](https://github.com/nathanrodman) +- **Azure Monitor :** Adding json formatting of error messages in Panel Header Corner and Inspect Error Tab. [#44877](https://github.com/grafana/grafana/pull/44877), [@yaelleC](https://github.com/yaelleC) +- **Azure Monitor:** Add 2 more Curated Dashboards for VM Insights. [#45187](https://github.com/grafana/grafana/pull/45187), [@jcolladokuri](https://github.com/jcolladokuri) +- **CloudWatch:** Handle new error codes for MetricInsights. [#47033](https://github.com/grafana/grafana/pull/47033), [@Gabrielopesantos](https://github.com/Gabrielopesantos) +- **Dashboards:** show changes in save dialog. [#46557](https://github.com/grafana/grafana/pull/46557), [@ryantxu](https://github.com/ryantxu) +- **DataSource:** Default data source is no longer a persisted state but just the default data source for new panels. [#45132](https://github.com/grafana/grafana/pull/45132), [@torkelo](https://github.com/torkelo) +- **DataSourcePlugin API:** Allow queries import when changing data source type. [#47435](https://github.com/grafana/grafana/pull/47435), [@dprokop](https://github.com/dprokop) +- **Explore:** Remove return to panel button. [#45018](https://github.com/grafana/grafana/pull/45018), [@gelicia](https://github.com/gelicia) +- **Explore:** allow users to save Explore state to a new panel in a new dashboard. [#45148](https://github.com/grafana/grafana/pull/45148), [@Elfo404](https://github.com/Elfo404) +- **Instrumentation:** Proxy status code correction and various improvements. [#47473](https://github.com/grafana/grafana/pull/47473), [@marefr](https://github.com/marefr) +- **Logging:** Introduce feature toggle to activate gokit/log format. [#47336](https://github.com/grafana/grafana/pull/47336), [@ying-jeanne](https://github.com/ying-jeanne) +- **NewsPanel:** Add support for Atom feeds. [#45390](https://github.com/grafana/grafana/pull/45390), [@kaydelaney](https://github.com/kaydelaney) +- **Plugins:** Add deprecation notice for /api/tsdb/query endpoint. [#45238](https://github.com/grafana/grafana/pull/45238), [@wbrowne](https://github.com/wbrowne) +- **Plugins:** Adding support for traceID field to accept variables. [#45559](https://github.com/grafana/grafana/pull/45559), [@vinisdl](https://github.com/vinisdl) +- **PostgreSQL:** \_\_unixEpochGroup to support arithmetic expression as argument. [#46764](https://github.com/grafana/grafana/pull/46764), [@s0nik42](https://github.com/s0nik42) +- **Profile/Help:** Expose option to disable profile section and help menu. [#46308](https://github.com/grafana/grafana/pull/46308), [@cameronwaterman](https://github.com/cameronwaterman) +- **Prometheus:** Enable new visual query builder by default. [#46634](https://github.com/grafana/grafana/pull/46634), [@torkelo](https://github.com/torkelo) +- **SAML:** Allow disabling of SAML signups. [#47481](https://github.com/grafana/grafana/pull/47481), [@mmandrus](https://github.com/mmandrus) +- **SAML:** Allow disabling of SAML signups. (Enterprise) +- **Table:** New pagination option. [#45732](https://github.com/grafana/grafana/pull/45732), [@zoltanbedi](https://github.com/zoltanbedi) +- **TablePanel:** Add cell inspect option. [#45620](https://github.com/grafana/grafana/pull/45620), [@dprokop](https://github.com/dprokop) +- **Tempo / Trace Viewer:** Support Span Links in Trace Viewer. [#45632](https://github.com/grafana/grafana/pull/45632), [@Shachi16](https://github.com/Shachi16) +- **Tempo:** Download span references in data inspector. [#47074](https://github.com/grafana/grafana/pull/47074), [@connorlindsey](https://github.com/connorlindsey) +- **Tempo:** Separate trace to logs and loki search datasource config. [#46655](https://github.com/grafana/grafana/pull/46655), [@connorlindsey](https://github.com/connorlindsey) +- **Trace View:** Show number of child spans. [#44393](https://github.com/grafana/grafana/pull/44393), [@tharun208](https://github.com/tharun208) +- **Transformations:** Support escaped characters in key-value pair parsing. [#47901](https://github.com/grafana/grafana/pull/47901), [@aangelisc](https://github.com/aangelisc) + +### Bug fixes + +- **Azure Monitor:** Bug Fix for incorrect variable cascading for template variables. [#47478](https://github.com/grafana/grafana/pull/47478), [@jcolladokuri](https://github.com/jcolladokuri) +- **CloudWatch:** List all metrics properly in SQL autocomplete. [#45898](https://github.com/grafana/grafana/pull/45898), [@sunker](https://github.com/sunker) +- **CloudWatch:** Run query on blur in logs query field. [#47454](https://github.com/grafana/grafana/pull/47454), [@fridgepoet](https://github.com/fridgepoet) +- **Dashboard:** Template variables are now correctly persisted when clicking breadcrumb links. [#46790](https://github.com/grafana/grafana/pull/46790), [@ashharrison90](https://github.com/ashharrison90) +- **DashboardPage:** Remember scroll position when coming back panel edit / view panel. [#47639](https://github.com/grafana/grafana/pull/47639), [@torkelo](https://github.com/torkelo) +- **Panel Edit:** Options search now works correctly when a logarithmic scale option is set. [#47927](https://github.com/grafana/grafana/pull/47927), [@ashharrison90](https://github.com/ashharrison90) +- **Postgres:** Return tables with hyphenated schemes. [#45754](https://github.com/grafana/grafana/pull/45754), [@zuchka](https://github.com/zuchka) +- **Table panel:** Fix horizontal scrolling when pagination is enabled. [#47776](https://github.com/grafana/grafana/pull/47776), [@dprokop](https://github.com/dprokop) +- **Variables:** Ensure variables in query params are correctly recognised. [#47049](https://github.com/grafana/grafana/pull/47049), [@ashharrison90](https://github.com/ashharrison90) +- **Variables:** Fix crash when changing query variable datasource. [#44957](https://github.com/grafana/grafana/pull/44957), [@joshhunt](https://github.com/joshhunt) +- **Visualizations:** Stack negative-valued series downwards. [#47373](https://github.com/grafana/grafana/pull/47373), [@leeoniya](https://github.com/leeoniya) + +### Breaking changes + +For a proxied request, e.g. Grafana's datasource or plugin proxy: + +- If the request is cancelled, e.g. from the browser/by the client, the HTTP status code is now `499 Client closed request` instead of `502 Bad gateway` +- If the request times out, e.g. takes longer time than allowed, the HTTP status code is now `504 Gateway timeout` instead of `502 Bad gateway`. Issue [#47473](https://github.com/grafana/grafana/issues/47473) + +The change in behavior is that negative-valued series are now stacked downwards from 0 (in their own stacks), rather than downwards from the top of the positive stacks. We now automatically group stacks by Draw style, Line interpolation, and Bar alignment, making it impossible to stack bars on top of lines, or smooth lines on top of stepped lines. Issue [#47373](https://github.com/grafana/grafana/issues/47373) + +The meaning of the default data source has now changed from being a persisted property in a panel. Before when you selected the default data source for a panel and later changed the default data source to another data source it would change all panels who were configured to use the default data source. From now on the default data source is just the default for new panels and changing the default will not impact any currently saved dashboards. Issue [#45132](https://github.com/grafana/grafana/issues/45132) + +The Tooltip component provided by `@grafana/ui` is no longer automatically interactive (that is you can hover onto it and click a link or select text). It will from now on by default close automatically when you mouse out from the trigger element. To make tooltips behave like before set the new `interactive` property to true. + Issue [#45053](https://github.com/grafana/grafana/issues/45053) + +### Deprecations + +`/api/tsdb/query` API has been deprecated and will be removed in a future release. Use [/api/ds/query](https://grafana.com/docs/grafana/latest/http_api/data_source/#query-a-data-source) instead. Issue [#45238](https://github.com/grafana/grafana/issues/45238) + +### Plugin development fixes & changes + +- **Card:** Increase clickable area when meta items are present. [#47935](https://github.com/grafana/grafana/pull/47935), [@ashharrison90](https://github.com/ashharrison90) +- **Loki:** Fix operator description propup from being shortened. [#46575](https://github.com/grafana/grafana/pull/46575), [@glintik](https://github.com/glintik) +- **Tooltips:** Make tooltips non interactive by default. [#45053](https://github.com/grafana/grafana/pull/45053), [@torkelo](https://github.com/torkelo) + + # 8.5.0-beta1 (2022-04-06) @@ -7,6 +85,7 @@ - Add config option to enable/disable reporting. (Enterprise) - **Alerting:** Accurately set value for prom-compatible APIs. [#47216](https://github.com/grafana/grafana/pull/47216), [@gotjosh](https://github.com/gotjosh) - **Alerting:** Provisioning API - Notification Policies. [#46755](https://github.com/grafana/grafana/pull/46755), [@alexweav](https://github.com/alexweav) +- **Alerting:** Notification URL points to alert view page instead of alert edit page. [#47752](https://github.com/grafana/grafana/pull/47752), [@joeblubaugh](https://github.com/joeblubaugh) - **Analytics:** Enable grafana and plugin update checks to be operated independently. [#46352](https://github.com/grafana/grafana/pull/46352), [@wbrowne](https://github.com/wbrowne) - **Azure Monitor:** Add support for multiple template variables in resource picker. [#46215](https://github.com/grafana/grafana/pull/46215), [@sarahzinger](https://github.com/sarahzinger) - **Caching:** Add separate TTL for resources cache. (Enterprise) @@ -24,6 +103,22 @@ When user is using Github OAuth, GitHub login is showed as both Grafana login an The meaning of the default data source has now changed from being a persisted property in a panel. Before when you selected the default data source for a panel and later changed the default data source to another data source it would change all panels who were configured to use the default data source. From now on the default data source is just the default for new panels and changing the default will not impact any currently saved dashboards. Issue [#45132](https://github.com/grafana/grafana/issues/45132) + + +# 8.4.7 (2022-04-19) + +### Features and enhancements + +- **CloudWatch:** Added missing MemoryDB Namespace metrics. [#47290](https://github.com/grafana/grafana/pull/47290), [@james-deee](https://github.com/james-deee) +- **Histogram Panel:** Take decimal into consideration. [#47330](https://github.com/grafana/grafana/pull/47330), [@mdvictor](https://github.com/mdvictor) +- **TimeSeries:** Sort tooltip values based on raw values. [#46738](https://github.com/grafana/grafana/pull/46738), [@dprokop](https://github.com/dprokop) + +### Bug fixes + +- **API:** Include userId, orgId, uname in request logging middleware. [#47183](https://github.com/grafana/grafana/pull/47183), [@marefr](https://github.com/marefr) +- **Elasticsearch:** Respect maxConcurrentShardRequests datasource setting. [#47120](https://github.com/grafana/grafana/pull/47120), [@alexandrst88](https://github.com/alexandrst88) + + diff --git a/Dockerfile b/Dockerfile index 711dc84fb05..455a24b0774 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY emails emails ENV NODE_ENV production RUN yarn build -FROM golang:1.17.8-alpine3.15 as go-builder +FROM golang:1.17.9-alpine3.15 as go-builder RUN apk add --no-cache gcc g++ make diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index a8c2760a639..f8ee00a412d 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -21,7 +21,7 @@ COPY emails emails ENV NODE_ENV production RUN yarn build -FROM golang:1.17.8 AS go-builder +FROM golang:1.17.9 AS go-builder WORKDIR /src/grafana diff --git a/conf/defaults.ini b/conf/defaults.ini index 904719d5620..024a7066a1c 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1140,6 +1140,9 @@ promQueryBuilder = true # Experimental Explore to Dashboard workflow explore2Dashboard = true +# Experimental Command Palette +commandPalette = true + # feature1 = true # feature2 = false @@ -1173,3 +1176,22 @@ default_baselayer_config = # Enable or disable loading other base map layers enable_custom_baselayers = true + +#################################### Dashboard previews ##################################### + +[dashboard_previews.crawler] +# Number of dashboards rendered in parallel. Default is 6. +thread_count = + +# Timeout passed down to the Image Renderer plugin. It is used in two separate places within a single rendering request: +# First during the initial navigation to the dashboard and then when waiting for all the panels to load. Default is 20s. +# This setting should be expressed as a duration. Examples: 10s (seconds), 1m (minutes). +rendering_timeout = + +# Maximum duration of a single crawl. Default is 1h. +# This setting should be expressed as a duration. Examples: 10s (seconds), 1m (minutes). +max_crawl_duration = + +# Minimum interval between two subsequent scheduler runs. Default is 12h. +# This setting should be expressed as a duration. Examples: 10s (seconds), 1m (minutes). +scheduler_interval = diff --git a/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json b/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json index ab94db3f055..f5751ec42f5 100644 --- a/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json +++ b/devenv/dev-dashboards/feature-templating/templating-dashboard-links-and-variables.json @@ -16,7 +16,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1646409057541, + "iteration": 1601526910610, "links": [ { "icon": "external link", @@ -73,61 +73,6 @@ "timeShift": null, "title": "${custom.text}", "type": "text" - }, - { - "id": 4, - "gridPos": { - "h": 9, - "w": 9, - "x": 12, - "y": 0 - }, - "type": "stat", - "title": "Panel Title", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "color": { - "mode": "thresholds" - }, - "links": [ - { - "title": "Var Link", - "url": "/d/vmie2cmWz/bar-gauge-demo?var-custom=$custom" - } - ] - }, - "overrides": [] - }, - "options": { - "reduceOptions": { - "values": false, - "calcs": [ - "lastNotNull" - ], - "fields": "" - }, - "orientation": "auto", - "textMode": "auto", - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto" - }, - "pluginVersion": "8.5.0-pre", - "datasource": null } ], "schemaVersion": 26, @@ -167,11 +112,6 @@ "selected": false, "text": "p3", "value": "p3" - }, - { - "selected": false, - "text": "p4", - "value": "test%25value" } ], "query": "p1,p2,p3", diff --git a/docs/sources/administration/service-accounts/add-service-account-token.md b/docs/sources/administration/service-accounts/add-service-account-token.md index a44a1a8d0db..49aaeb2153d 100644 --- a/docs/sources/administration/service-accounts/add-service-account-token.md +++ b/docs/sources/administration/service-accounts/add-service-account-token.md @@ -14,7 +14,7 @@ You can create a service account token using the Grafana UI or via the API. For ## Before you begin -- Ensure you have added the `service-accounts` feature toggle to Grafana. For more information about adding the `service-accounts` feature toggle, refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}). +- Ensure you have added the `serviceAccounts` feature toggle to Grafana. For more information about adding the feature toggle, refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}). - Ensure you have permission to create and edit service accounts. For more information about user roles, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}). - [Create a service account in Grafana]({{< relref "./create-service-account.md#">}}). diff --git a/docs/sources/administration/service-accounts/create-service-account.md b/docs/sources/administration/service-accounts/create-service-account.md index bc2909967ba..0d2a7a1b2b1 100644 --- a/docs/sources/administration/service-accounts/create-service-account.md +++ b/docs/sources/administration/service-accounts/create-service-account.md @@ -15,7 +15,7 @@ For more information about creating service accounts via the API, refer to [Crea ## Before you begin -- Ensure you have added the feature toggle for service accounts `service-accounts`. For more information about adding the `service-account` feature toggle, refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}). +- Ensure you have added the feature toggle for service accounts `serviceAccounts`. For more information about adding the feature toggle, refer to [Enable service accounts]({{< relref "./enable-service-accounts.md#">}}). - Ensure you have permission to create and edit service accounts. For more information about user permissions, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}). **To create a service account:** diff --git a/docs/sources/administration/service-accounts/enable-service-accounts.md b/docs/sources/administration/service-accounts/enable-service-accounts.md index 63d66210939..b9e1588f690 100644 --- a/docs/sources/administration/service-accounts/enable-service-accounts.md +++ b/docs/sources/administration/service-accounts/enable-service-accounts.md @@ -22,12 +22,12 @@ You can enable service accounts by: This topic shows you how to enable service accounts by modifying the Grafana configuration file. 1. Sign in to the Grafana server and locate the configuration file. For more information about finding the configuration file, refer to LINK. -1. Open the configuration file and locate the [feature toggles] section. In your [config file]({{< relref "../../administration/configuration.md#config-file-locations" >}}), add `service-accounts` as a [feature_toggle]({{< relref "../../administration/configuration.md#feature_toggle" >}}). +1. Open the configuration file and locate the [feature toggles] section. In your [config file]({{< relref "../../administration/configuration.md#config-file-locations" >}}), add `serviceAccounts` as a [feature_toggle]({{< relref "../../administration/configuration.md#feature_toggle" >}}). ``` [feature_toggles] # enable features, separated by spaces -enable = service-accounts +enable = serviceAccounts ``` 1. Save your changes, Grafana should recognize your changes; in case of any issues we recommend restarting the Grafana server. @@ -38,6 +38,6 @@ This topic shows you how to enable service accounts by setting environment varia > **Note:** Environment variables override any configuration file settings. -You can use `GF_FEATURE_TOGGLES_ENABLE = service-accounts` environment variable. +You can use `GF_FEATURE_TOGGLES_ENABLE = serviceAccounts` environment variable. For more information regarding on how to setup environment variables refer to [Configuring with environment variables]({{< relref "../../administration/configuration.md#override-configuration-with-environment-variables" >}}). diff --git a/docs/sources/alerting/unified-alerting/_index.md b/docs/sources/alerting/unified-alerting/_index.md index 2836b133608..de360455048 100644 --- a/docs/sources/alerting/unified-alerting/_index.md +++ b/docs/sources/alerting/unified-alerting/_index.md @@ -8,6 +8,8 @@ weight = 113 Grafana 8.0 has new and improved alerting that centralizes alerting information in a single, searchable view. It is enabled by default for all new OSS instances, and is an [opt-in]({{< relref "./opt-in.md" >}}) feature for older installations that still use legacy dashboard alerting. We encourage you to create issues in the Grafana GitHub repository for bugs found while testing Grafana alerting. See also, [What's New with Grafana alerting]({{< relref "./difference-old-new.md" >}}). +> Refer to [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) in Grafana Enterprise to learn more about controlling access to alerts using fine-grained permissions. + When Grafana alerting is enabled, you can: - [Create Grafana managed alerting rules]({{< relref "alerting-rules/create-grafana-managed-rule.md" >}}) diff --git a/docs/sources/best-practices/dashboard-management-maturity-levels.md b/docs/sources/best-practices/dashboard-management-maturity-levels.md index 268cd83d7dd..17ca84905e6 100644 --- a/docs/sources/best-practices/dashboard-management-maturity-levels.md +++ b/docs/sources/best-practices/dashboard-management-maturity-levels.md @@ -46,7 +46,7 @@ How can you tell you are here? - Compare like to like: split service dashboards when the magnitude differs. Make sure aggregated metrics don't drown out important information. - Expressive charts with meaningful use of color and normalizing axes where you can. - - Example of meaningful color: Blue means it's good, red means it's bad. [Thresholds]({{< relref "../panels/specify-thresholds/about-thresholds.md" >}}) can help with that. + - Example of meaningful color: Blue means it's good, red means it's bad. [Thresholds]({{< relref "../panels/configure-thresholds" >}}) can help with that. - Example of normalizing axes: When comparing CPU usage, measure by percentage rather than raw number, because machines can have a different number of cores. Normalizing CPU usage by the number of cores reduces cognitive load because the viewer can trust that at 100% all cores are being used, without having to know the number of CPUs. - Directed browsing cuts down on "guessing." - Template variables make it harder to “just browse” randomly or aimlessly. diff --git a/docs/sources/dashboards/_index.md b/docs/sources/dashboards/_index.md index 672c34883df..0c73e1a8696 100644 --- a/docs/sources/dashboards/_index.md +++ b/docs/sources/dashboards/_index.md @@ -13,7 +13,7 @@ Dashboard snapshots are static . Queries and expressions cannot be re-executed f Before you begin, ensure that you have configured a data source. See also: - [Working with Grafana dashboard UI]({{< relref "./dashboard-ui/_index.md" >}}) -- [Dashboard folders]({{< relref "./dashboard-folders.md" >}}) +- [Dashboard folders]({{< relref "./dashboard_folders.md" >}}) - [Create dashboard]({{< relref "./dashboard-create" >}}) - [Manage dashboards]({{< relref "./dashboard-manage.md" >}}) - [Annotations]({{< relref "./annotations.md" >}}) @@ -22,7 +22,7 @@ Before you begin, ensure that you have configured a data source. See also: - [Keyboard shortcuts]({{< relref "./shortcuts.md" >}}) - [Reporting]({{< relref "./reporting.md" >}}) - [Time range controls]({{< relref "./time-range-controls.md" >}}) -- [Dashboard version history]({{< relref "./dashboard-history.md" >}}) +- [Dashboard version history]({{< relref "./dashboard_history.md" >}}) - [Dashboard export and import]({{< relref "./export-import.md" >}}) - [Dashboard JSON model]({{< relref "./json-model.md" >}}) - [Scripted dashboards]({{< relref "./scripted-dashboards.md" >}}) diff --git a/docs/sources/dashboards/time-range-controls.md b/docs/sources/dashboards/time-range-controls.md index 34a72186be5..7b170f2de2a 100644 --- a/docs/sources/dashboards/time-range-controls.md +++ b/docs/sources/dashboards/time-range-controls.md @@ -117,7 +117,7 @@ For more advanced time settings, click the **Dashboard settings** (gear) icon at ## Panel time overrides and timeshift -In [Query options]({{< relref "../panels/reference-query-options.md" >}}), you can override the relative time range for individual panels, causing them to be different than what is selected in the dashboard time picker in the upper right. This allows you to show metrics from different time periods or days at the same time. +In [Query options]({{< relref "../panels/query-options.md" >}}), you can override the relative time range for individual panels, causing them to be different than what is selected in the dashboard time picker in the upper right. This allows you to show metrics from different time periods or days at the same time. > **Note:** Panel time overrides have no effect when the time range for the dashboard is absolute. diff --git a/docs/sources/datasources/mssql.md b/docs/sources/datasources/mssql.md index f56a01070f1..31c1e19229d 100644 --- a/docs/sources/datasources/mssql.md +++ b/docs/sources/datasources/mssql.md @@ -174,7 +174,7 @@ A time series query result is returned in a [wide data frame format]({{< relref > For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below. -You can optionally customize the default series name formatting using instructions in [Reference: Standard field definitions]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}). +To optionally customize the default series name formatting, refer to [Standard field definitions]({{< relref "../panels/standard-field-definitions.md#display-name" >}}). **Example with `metric` column:** @@ -218,7 +218,7 @@ GROUP BY ORDER BY 1 ``` -Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Reference: Standard field definitions]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}) display name value of `${__field.labels.hostname}`. +Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard field definition]({{< relref "../panels/standard-field-definitions.md#display-name" >}}) display name value of `${__field.labels.hostname}`. Data frame result: diff --git a/docs/sources/datasources/mysql.md b/docs/sources/datasources/mysql.md index 957cf7e3770..5bbb1264ca3 100644 --- a/docs/sources/datasources/mysql.md +++ b/docs/sources/datasources/mysql.md @@ -186,7 +186,7 @@ A time series query result is returned in a [wide data frame format]({{< relref > For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below. -You can optionally customize the default series name formatting using instructions in [Reference: Standard field definitions]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}). +To optionally customize the default series name formatting, refer to [Standard field definitions]({{< relref "../panels/standard-field-definitions.md#display-name" >}}). **Example with `metric` column:** @@ -228,7 +228,7 @@ GROUP BY time, hostname ORDER BY time ``` -Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Reference: Standard field definitions]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}) display value of `${__field.labels.hostname}`. +Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard field definition]({{< relref "../panels/standard-field-definitions.md#display-name" >}}) display value of `${__field.labels.hostname}`. Data frame result: diff --git a/docs/sources/datasources/postgres.md b/docs/sources/datasources/postgres.md index 24f9fd3456a..f5f335689f0 100644 --- a/docs/sources/datasources/postgres.md +++ b/docs/sources/datasources/postgres.md @@ -191,7 +191,7 @@ A time series query result is returned in a [wide data frame format]({{< relref > For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below. -You can optionally customize the default series name formatting using instructions in [Reference: Standard field definitions]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}). +To optionally customize the default series name formatting, refer to [Standard field definitions]({{< relref "../panels/standard-field-definitions.md#display-name" >}}). **Example with `metric` column:** @@ -233,7 +233,7 @@ GROUP BY time, hostname ORDER BY time ``` -Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Reference: Standard field definitions]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}) display value of `${__field.labels.hostname}`. +Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard field definition]({{< relref "../panels/standard-field-definitions.md#display-name" >}}) display value of `${__field.labels.hostname}`. Data frame result: diff --git a/docs/sources/developers/plugins/data-frames.md b/docs/sources/developers/plugins/data-frames.md index 1c85e563c85..fb4554f0d34 100644 --- a/docs/sources/developers/plugins/data-frames.md +++ b/docs/sources/developers/plugins/data-frames.md @@ -163,7 +163,7 @@ Dimensions: 5 fields by 2 rows +---------------------+------------------+------------------+------------------+------------------+ ``` -> **Note:** Not all panels support the wide time series data frame format. To keep full backward compatibility we have introduced a transformation that can be used to convert from the wide to the long format. Read more about how to use it here: [Prepare time series-transformation]({{< relref "../../panels/reference-transformation-functions.md#prepare-time-series" >}}). +> **Note:** Not all panels support the wide time series data frame format. To keep full backward compatibility we have introduced a transformation that can be used to convert from the wide to the long format. For usage information, refer to the [Prepare time series-transformation]({{< relref "../../panels/transform-data/transformation-functions.md#prepare-time-series" >}}). ## Technical references diff --git a/docs/sources/enterprise/access-control/_index.md b/docs/sources/enterprise/access-control/_index.md index f9fa9d56690..a70c9aa3801 100644 --- a/docs/sources/enterprise/access-control/_index.md +++ b/docs/sources/enterprise/access-control/_index.md @@ -39,6 +39,7 @@ Fine-grained access control is available for the following capabilities: - [Manage teams]({{< relref "../../administration/manage-users-and-permissions/manage-teams/_index.md" >}}) - [Manage dashboards and folders]({{< relref "../../dashboards/_index.md" >}}) - [Manage annotations]({{< relref "../../visualizations/annotations.md" >}}) +- [Alerting]({{< relref "../../alerting/unified-alerting/_index.md">}}) To learn about specific endpoints where you can use fine-grained access control, refer to [Permissions]({{< relref "./permissions.md" >}}) and to the relevant [API]({{< relref "../../http_api/_index.md" >}}) documentation. diff --git a/docs/sources/enterprise/access-control/fine-grained-access-control-references.md b/docs/sources/enterprise/access-control/fine-grained-access-control-references.md index fd77a19dde6..e2222cffb6a 100644 --- a/docs/sources/enterprise/access-control/fine-grained-access-control-references.md +++ b/docs/sources/enterprise/access-control/fine-grained-access-control-references.md @@ -54,11 +54,34 @@ The reference information that follows complements conceptual information about | `fixed:annotations.dashboard:writer` | `annotations:write`
`annotations.create`
`annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. | | `fixed:annotations:writer` | `annotations:write`
`annotations.create`
`annotations:delete` for scope `annotations:type:*` | Create, update and delete all annotations and annotation tags. | +### Alerting roles + +If you [enable]({{< relref "../../alerting/unified-alerting/opt-in.md" >}}) Grafana Alerting, you can use predefined roles to manage user access to alert rules, alert instances, and alert notification settings and create custom roles to limit user access to alert rules in a folder. + +Access to Grafana alert rules is an intersection of many permissions: + +- Permission to read a folder, for example, the fixed role `fixed:folders:reader` or action `folders:read` in the scope of a folder `folders:id:` +- Permission to manage alerts. The following table contains information about alerting fixed roles. +- Permission to query **all** data sources that the rule uses, for example, the fixed role `fixed:datasources:reader` or action `datasources:query` in the scope of `datasources:uid:`. + +For more information about the permissions required to access alert rules, refer to [Create a custom role to access alerts in a folder]({{< relref "./usage-scenarios.md#create-a-custom-role-to-access-alerts-in-a-folder" >}}). + +| Fixed roles | Permissions | Descriptions | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fixed:alerting.rules:reader` | `alert.rule:read` for scope `folders:*`
`alert.rules.external:read` for scope `datasources:*` | Read all\* Grafana, Mimir, and Loki alert rules | +| `fixed:alerting.rules:editor` | All permissions from `fixed:alerting.rules:reader` and
`alert.rule:create`
`alert.rule:update`
`alert.rule:delete` for scope `folders:*`
`alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules. | +| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope
`alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences. | +| `fixed:alerting.instances:editor` | All permissions from `fixed:alerting.instances:reader` and
`alert.instances:create`
`alert.instances:update` for organization scope
`alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki. | +| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies. | +| `fixed:alerting.notifications:editor` | All permissions from `fixed:alerting.notifications:reader` and
`alert.notifications:create`
`alert.notifications:update`
`alert.notifications:delete` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager. | +| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader`
`fixed:alerting.instances:reader`
`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies. | +| `fixed:alerting:editor` | All permissions from `fixed:alerting.rules:editor`
`fixed:alerting.instances:editor`
`fixed:alerting.notifications:editor` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies. | + ## Default built-in role assignments -| Built-in role | Associated role | Description | -| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Grafana Admin | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer` | Default [Grafana server administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#grafana-server-administrators" >}}) assignments. | -| Admin | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:folders:reader`
`fixes:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer` | Default [Grafana organization administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. | -| Editor | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled | Default [Editor]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. | -| Viewer | `fixed:datasources:id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer` | Default [Viewer]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. | +| Built-in role | Associated role | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Grafana Admin | `fixed:roles:reader`
`fixed:roles:writer`
`fixed:users:reader`
`fixed:users:writer`
`fixed:org.users:reader`
`fixed:org.users:writer`
`fixed:ldap:reader`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:reader`
`fixed:organization:maintainer`
`fixed:licensing:reader`
`fixed:licensing:writer` | Default [Grafana server administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#grafana-server-administrators" >}}) assignments. | +| Admin | `fixed:reports:reader`
`fixed:reports:writer`
`fixed:datasources:reader`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:reader`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:reader`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:reader`
`fixed:dashboards.permissions:writer`
`fixed:folders:reader`
`fixes:folders:writer`
`fixed:folders.permissions:reader`
`fixed:folders.permissions:writer`
`fixed:alerting:editor` | Default [Grafana organization administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. | +| Editor | `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled
`fixed:alerting:editor` | Default [Editor]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. | +| Viewer | `fixed:datasources:id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader` | Default [Viewer]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. | diff --git a/docs/sources/enterprise/access-control/permissions.md b/docs/sources/enterprise/access-control/permissions.md index 8cd6ae0c2a4..9f3bf2e009e 100644 --- a/docs/sources/enterprise/access-control/permissions.md +++ b/docs/sources/enterprise/access-control/permissions.md @@ -23,100 +23,117 @@ scope The following list contains fine-grained access control actions. -| Action | Applicable scope | Description | -| ------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `roles:list` | `roles:*` | List available roles without permissions. | -| `roles:read` | `roles:*`
`roles:uid:*` | Read a specific role with its permissions. | -| `roles:write` | `permissions:delegate` | Create or update a custom role. | -| `roles:delete` | `permissions:delegate` | Delete a custom role. | -| `roles.builtin:list` | `roles:*` | List built-in role assignments. | -| `roles.builtin:add` | `permissions:delegate` | Create a built-in role assignment. | -| `roles.builtin:remove` | `permissions:delegate` | Delete a built-in role assignment. | -| `reports.admin:create` | n/a | Create reports. | -| `reports.admin:write` | `reports:*`
`reports:id:*` | Update reports. | -| `reports:delete` | `reports:*`
`reports:id:*` | Delete reports. | -| `reports:read` | `reports:*` | List all available reports or get a specific report. | -| `reports:send` | `reports:*` | Send a report email. | -| `reports.settings:write` | n/a | Update report settings. | -| `reports.settings:read` | n/a | Read report settings. | -| `provisioning:reload` | `provisioners:*` | Reload provisioning files. To find the exact scope for specific provisioner, see [Scope definitions]({{< relref "./permissions.md#scope-definitions" >}}). | -| `teams.roles:list` | `teams:*` | List roles assigned directly to a team. | -| `teams.roles:add` | `permissions:delegate` | Assign a role to a team. | -| `teams.roles:remove` | `permissions:delegate` | Unassign a role from a team. | -| `users:read` | `global.users:*` | Read or search user profiles. | -| `users:write` | `global.users:*`
`global.users:id:*` | Update a user’s profile. | -| `users.teams:read` | `global.users:*`
`global.users:id:*` | Read a user’s teams. | -| `users.authtoken:list` | `global.users:*`
`global.users:id:*` | List authentication tokens that are assigned to a user. | -| `users.authtoken:update` | `global.users:*`
`global.users:id:*` | Update authentication tokens that are assigned to a user. | -| `users.password:update` | `global.users:*`
`global.users:id:*` | Update a user’s password. | -| `users:delete` | `global.users:*`
`global.users:id:*` | Delete a user. | -| `users:create` | n/a | Create a user. | -| `users:enable` | `globa.users:*`
`global.users:id:*` | Enable a user. | -| `users:disable` | `global.users:*`
`global.users:id:*` | Disable a user. | -| `users.permissions:update` | `global.users:*`
`global.users:id:*` | Update a user’s organization-level permissions. | -| `users:logout` | `global.users:*`
`global.users:id:*` | Sign out a user. | -| `users.quotas:list` | `global.users:*`
`global.users:id:*` | List a user’s quotas. | -| `users.quotas:update` | `global.users:*`
`global.users:id:*` | Update a user’s quotas. | -| `users.roles:list` | `users:*` | List roles assigned directly to a user. | -| `users.roles:add` | `permissions:delegate` | Assign a role to a user. | -| `users.roles:remove` | `permissions:delegate` | Unassign a role from a user. | -| `users.permissions:list` | `users:*` | List permissions of a user. | -| `org.users:read` | `users:*`
`users:id:*` | Get user profiles within an organization. | -| `org.users:add` | `users:*` | Add a user to an organization. | -| `org.users:remove` | `users:*`
`users:id:*` | Remove a user from an organization. | -| `org.users.role:update` | `users:*`
`users:id:*` | Update the organization role (`Viewer`, `Editor`, or `Admin`) of an organization. | -| `orgs:read` | `orgs:*`
`orgs:id:*` | Read one or more organizations. | -| `orgs:write` | `orgs:*`
`orgs:id:*` | Update one or more organizations. | -| `org:create` | n/a | Create an organization. | -| `orgs:delete` | `orgs:*`
`orgs:id:*` | Delete one or more organizations. | -| `orgs.quotas:read` | `orgs:*`
`orgs:id:*` | Read organization quotas. | -| `orgs.quotas:write` | `orgs:*`
`orgs:id:*` | Update organization quotas. | -| `orgs.preferences:read` | `orgs:*`
`orgs:id:*` | Read organization preferences. | -| `orgs.preferences:write` | `orgs:*`
`orgs:id:*` | Update organization preferences. | -| `ldap.user:read` | n/a | Read users via LDAP. | -| `ldap.user:sync` | n/a | Sync users via LDAP. | -| `ldap.status:read` | n/a | Verify the availability of the LDAP server or servers. | -| `ldap.config:reload` | n/a | Reload the LDAP configuration. | -| `status:accesscontrol` | `services:accesscontrol` | Get access-control enabled status. | -| `settings:read` | `settings:*`
`settings:auth.saml:*`
`settings:auth.saml:enabled` (property level) | Read the [Grafana configuration settings]({{< relref "../../administration/configuration/_index.md" >}}) | -| `settings:write` | `settings:*`
`settings:auth.saml:*`
`settings:auth.saml:enabled` (property level) | Update any Grafana configuration settings that can be [updated at runtime]({{< relref "../../enterprise/settings-updates/_index.md" >}}). | -| `server.stats:read` | n/a | Read Grafana instance statistics. | -| `datasources:explore` | n/a | Enable access to the **Explore** tab. | -| `datasources:read` | n/a
`datasources:*`
`datasources:id:*`
`datasources:uid:*`
`datasources:name:*` | List data sources. | -| `datasources:query` | n/a
`datasources:*`
`datasources:id:*` | Query data sources. | -| `datasources.id:read` | `datasources:*`
`datasources:name:*` | Read data source IDs. | -| `datasources:create` | n/a | Create data sources. | -| `datasources:write` | `datasources:*`
`datasources:id:*` | Update data sources. | -| `datasources:delete` | `datasources:id:*`
`datasources:uid:*`
`datasources:name:*` | Delete data sources. | -| `datasources.permissions:read` | `datasources:*`
`datasources:id:*` | List data source permissions. | -| `datasources.permissions:write` | `datasources:*`
`datasources:id:*` | Update data source permissions. | -| `licensing:read` | n/a | Read licensing information. | -| `licensing:update` | n/a | Update the license token. | -| `licensing:delete` | n/a | Delete the license token. | -| `licensing.reports:read` | n/a | Get custom permission reports. | -| `teams:create` | n/a | Create teams. | -| `teams:read` | `teams:*`
`teams:id:*` | Read one or more teams and team preferences. | -| `teams:write` | `teams:*`
`teams:id:*` | Update one or more teams and team preferences. | -| `teams:delete` | `teams:*`
`teams:id:*` | Delete one or more teams. | -| `teams.permissions:read` | `teams:*`
`teams:id:*` | Read members and External Group Synchronization setup for teams. | -| `teams.permissions:write` | `teams:*`
`teams:id:*` | Add, remove and update members and manage External Group Synchronization setup for teams. | -| `dashboards:read` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Read one or more dashboards. | -| `dashboards:create` | `folders:*`
`folders:id:*` | Create dashboards in one or more folders. | -| `dashboards:write` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Update one or more dashboards. | -| `dashboards:edit` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Edit one or more dashboards (only in ui). | -| `dashboards:delete` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Delete one or more dashboards. | -| `dashboards.permissions:read` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Read permissions for one or more dashboards. | -| `dashboards.permissions:write` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Update permissions for one or more dashboards. | -| `folders:read` | `folders:*`
`folders:id:*` | Read one or more folders. | -| `folders:create` | n/a | Create folders. | -| `folders:write` | `folders:*`
`folders:id:*` | Update one or more folders. | -| `folders:delete` | `folders:*`
`folders:id:*` | Delete one or more folders. | -| `folers.permissions:read` | `folders:*`
`folders:id:*` | Read permissions for one or more folders. | -| `folders.permissions:write` | `folders:*`
`folders:id:*` | Update permissions for one or more folders. | -| `annotations.read` | `annotations:*`
`annotations:type:*` | Read annotations and annotation tags. | -| `annotations.create` | `annotations:*`
`annotations:type:*` | Create annotations. | -| `annotations.write` | `annotations:*`
`annotations:type:*` | Update annotations. | -| `annotations.delete` | `annotations:*`
`annotations:type:*` | Delete annotations. | +| Action | Applicable scope | Description | +| ------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `roles:list` | `roles:*` | List available roles without permissions. | +| `roles:read` | `roles:*`
`roles:uid:*` | Read a specific role with its permissions. | +| `roles:write` | `permissions:delegate` | Create or update a custom role. | +| `roles:delete` | `permissions:delegate` | Delete a custom role. | +| `roles.builtin:list` | `roles:*` | List built-in role assignments. | +| `roles.builtin:add` | `permissions:delegate` | Create a built-in role assignment. | +| `roles.builtin:remove` | `permissions:delegate` | Delete a built-in role assignment. | +| `reports.admin:create` | n/a | Create reports. | +| `reports.admin:write` | `reports:*`
`reports:id:*` | Update reports. | +| `reports:delete` | `reports:*`
`reports:id:*` | Delete reports. | +| `reports:read` | `reports:*` | List all available reports or get a specific report. | +| `reports:send` | `reports:*` | Send a report email. | +| `reports.settings:write` | n/a | Update report settings. | +| `reports.settings:read` | n/a | Read report settings. | +| `provisioning:reload` | `provisioners:*` | Reload provisioning files. To find the exact scope for specific provisioner, see [Scope definitions]({{< relref "./permissions.md#scope-definitions" >}}). | +| `teams.roles:list` | `teams:*` | List roles assigned directly to a team. | +| `teams.roles:add` | `permissions:delegate` | Assign a role to a team. | +| `teams.roles:remove` | `permissions:delegate` | Unassign a role from a team. | +| `users:read` | `global.users:*` | Read or search user profiles. | +| `users:write` | `global.users:*`
`global.users:id:*` | Update a user’s profile. | +| `users.teams:read` | `global.users:*`
`global.users:id:*` | Read a user’s teams. | +| `users.authtoken:list` | `global.users:*`
`global.users:id:*` | List authentication tokens that are assigned to a user. | +| `users.authtoken:update` | `global.users:*`
`global.users:id:*` | Update authentication tokens that are assigned to a user. | +| `users.password:update` | `global.users:*`
`global.users:id:*` | Update a user’s password. | +| `users:delete` | `global.users:*`
`global.users:id:*` | Delete a user. | +| `users:create` | n/a | Create a user. | +| `users:enable` | `globa.users:*`
`global.users:id:*` | Enable a user. | +| `users:disable` | `global.users:*`
`global.users:id:*` | Disable a user. | +| `users.permissions:update` | `global.users:*`
`global.users:id:*` | Update a user’s organization-level permissions. | +| `users:logout` | `global.users:*`
`global.users:id:*` | Sign out a user. | +| `users.quotas:list` | `global.users:*`
`global.users:id:*` | List a user’s quotas. | +| `users.quotas:update` | `global.users:*`
`global.users:id:*` | Update a user’s quotas. | +| `users.roles:list` | `users:*` | List roles assigned directly to a user. | +| `users.roles:add` | `permissions:delegate` | Assign a role to a user. | +| `users.roles:remove` | `permissions:delegate` | Unassign a role from a user. | +| `users.permissions:list` | `users:*` | List permissions of a user. | +| `org.users:read` | `users:*`
`users:id:*` | Get user profiles within an organization. | +| `org.users:add` | `users:*` | Add a user to an organization. | +| `org.users:remove` | `users:*`
`users:id:*` | Remove a user from an organization. | +| `org.users.role:update` | `users:*`
`users:id:*` | Update the organization role (`Viewer`, `Editor`, or `Admin`) of an organization. | +| `orgs:read` | `orgs:*`
`orgs:id:*` | Read one or more organizations. | +| `orgs:write` | `orgs:*`
`orgs:id:*` | Update one or more organizations. | +| `org:create` | n/a | Create an organization. | +| `orgs:delete` | `orgs:*`
`orgs:id:*` | Delete one or more organizations. | +| `orgs.quotas:read` | `orgs:*`
`orgs:id:*` | Read organization quotas. | +| `orgs.quotas:write` | `orgs:*`
`orgs:id:*` | Update organization quotas. | +| `orgs.preferences:read` | `orgs:*`
`orgs:id:*` | Read organization preferences. | +| `orgs.preferences:write` | `orgs:*`
`orgs:id:*` | Update organization preferences. | +| `ldap.user:read` | n/a | Read users via LDAP. | +| `ldap.user:sync` | n/a | Sync users via LDAP. | +| `ldap.status:read` | n/a | Verify the availability of the LDAP server or servers. | +| `ldap.config:reload` | n/a | Reload the LDAP configuration. | +| `status:accesscontrol` | `services:accesscontrol` | Get access-control enabled status. | +| `settings:read` | `settings:*`
`settings:auth.saml:*`
`settings:auth.saml:enabled` (property level) | Read the [Grafana configuration settings]({{< relref "../../administration/configuration/_index.md" >}}) | +| `settings:write` | `settings:*`
`settings:auth.saml:*`
`settings:auth.saml:enabled` (property level) | Update any Grafana configuration settings that can be [updated at runtime]({{< relref "../../enterprise/settings-updates/_index.md" >}}). | +| `server.stats:read` | n/a | Read Grafana instance statistics. | +| `datasources:explore` | n/a | Enable access to the **Explore** tab. | +| `datasources:read` | n/a
`datasources:*`
`datasources:id:*`
`datasources:uid:*`
`datasources:name:*` | List data sources. | +| `datasources:query` | n/a
`datasources:*`
`datasources:id:*` | Query data sources. | +| `datasources.id:read` | `datasources:*`
`datasources:name:*` | Read data source IDs. | +| `datasources:create` | n/a | Create data sources. | +| `datasources:write` | `datasources:*`
`datasources:id:*` | Update data sources. | +| `datasources:delete` | `datasources:id:*`
`datasources:uid:*`
`datasources:name:*` | Delete data sources. | +| `datasources.permissions:read` | `datasources:*`
`datasources:id:*` | List data source permissions. | +| `datasources.permissions:write` | `datasources:*`
`datasources:id:*` | Update data source permissions. | +| `licensing:read` | n/a | Read licensing information. | +| `licensing:update` | n/a | Update the license token. | +| `licensing:delete` | n/a | Delete the license token. | +| `licensing.reports:read` | n/a | Get custom permission reports. | +| `teams:create` | n/a | Create teams. | +| `teams:read` | `teams:*`
`teams:id:*` | Read one or more teams and team preferences. | +| `teams:write` | `teams:*`
`teams:id:*` | Update one or more teams and team preferences. | +| `teams:delete` | `teams:*`
`teams:id:*` | Delete one or more teams. | +| `teams.permissions:read` | `teams:*`
`teams:id:*` | Read members and External Group Synchronization setup for teams. | +| `teams.permissions:write` | `teams:*`
`teams:id:*` | Add, remove and update members and manage External Group Synchronization setup for teams. | +| `dashboards:read` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Read one or more dashboards. | +| `dashboards:create` | `folders:*`
`folders:id:*` | Create dashboards in one or more folders. | +| `dashboards:write` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Update one or more dashboards. | +| `dashboards:edit` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Edit one or more dashboards (only in ui). | +| `dashboards:delete` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Delete one or more dashboards. | +| `dashboards.permissions:read` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Read permissions for one or more dashboards. | +| `dashboards.permissions:write` | `dashboards:*`
`dashboards:id:*`
`folders:*`
`folders:id:*` | Update permissions for one or more dashboards. | +| `folders:read` | `folders:*`
`folders:id:*` | Read one or more folders. | +| `folders:create` | n/a | Create folders. | +| `folders:write` | `folders:*`
`folders:id:*` | Update one or more folders. | +| `folders:delete` | `folders:*`
`folders:id:*` | Delete one or more folders. | +| `folers.permissions:read` | `folders:*`
`folders:id:*` | Read permissions for one or more folders. | +| `folders.permissions:write` | `folders:*`
`folders:id:*` | Update permissions for one or more folders. | +| `annotations.read` | `annotations:*`
`annotations:type:*` | Read annotations and annotation tags. | +| `annotations.create` | `annotations:*`
`annotations:type:*` | Create annotations. | +| `annotations.write` | `annotations:*`
`annotations:type:*` | Update annotations. | +| `annotations.delete` | `annotations:*`
`annotations:type:*` | Delete annotations. | +| `alert.rules:read` | `folders:*`
`folders:id:*` | Read Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. | +| `alert.rules:create` | `folders:*`
`folders:id:*` | Create Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. | +| `alert.rules:update` | `folders:*`
`folders:id:*` | Update Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. | +| `alert.rules:delete` | `folders:*`
`folders:id:*` | Delete Grafana alert rules in a folder. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. | +| `alert.rules.external:read` | `datasources:*`
`datasources:uid:*` | Read alert rules in data sources that support alerting (Prometheus, Mimir, and Loki) | +| `alert.rules.external:write` | `datasources:*`
`datasources:uid:*` | Create, update, and delete alert rules in data sources that support alerting (Mimir and Loki). | +| `alert.instances:read` | n/a | Read alerts and silences in the current organization. | +| `alert.instances:create` | n/a | Create silences in the current organization. | +| `alert.instances:update` | n/a | Update and expire silences in the current organization. | +| `alert.instances.external:read` | `datasources:*`
`datasources:uid:*` | Read alerts and silences in data sources that support alerting. | +| `alert.instances.external:write` | `datasources:*`
`datasources:uid:*` | Manage alerts and silences in data sources that support alerting. | +| `alert.notifications:create` | n/a | Create templates, contact points, notification policies, and mute timings in the current organization. | +| `alert.notifications:read` | n/a | Read all templates, contact points, notification policies, and mute timings in the current organization. | +| `alert.notifications:update` | n/a | Update templates, contact points, notification policies, and mute timings in the current organization. | +| `alert.notifications:delete` | n/a | Delete templates, contact points, notification policies, and mute timings in the current organization. | +| `alert.notifications.external:read` | `datasources:*`
`datasources:uid:*` | Read templates, contact points, notification policies, and mute timings in data sources that support alerting. | +| `alert.notifications.external:write` | `datasources:*`
`datasources:uid:*` | Manage templates, contact points, notification policies, and mute timings in data sources that support alerting. | ## Scope definitions diff --git a/docs/sources/enterprise/access-control/usage-scenarios.md b/docs/sources/enterprise/access-control/usage-scenarios.md index b0f9200f3e4..5f44c447eeb 100644 --- a/docs/sources/enterprise/access-control/usage-scenarios.md +++ b/docs/sources/enterprise/access-control/usage-scenarios.md @@ -231,3 +231,46 @@ By default, the Grafana Server Admin is the only user who can create and manage 1. [Create a custom role]({{< ref "#create-your-custom-role" >}}) with `roles.builtin:add` and `roles:write` permissions, then create a built-in role assignment for `Editor` organization role. Note that any user with the ability to modify roles can only create, update or delete roles with permissions they themselves have been granted. For example, a user with the `Editor` role would be able to create and manage roles only with the permissions they have, or with a subset of them. + +## Create a custom role to access alerts in a folder + +To see an alert rule in Grafana, the user must have read access to the folder that stores the alert rule, permission to read alerts in the folder, and permission to query all data sources that the rule uses. + +The API command in this example is based on the following: + +- A `Test-Folder` with ID `92` +- Two data sources: `DS1` with UID `_oAfGYUnk`, and `DS2` with UID `YYcBGYUnk` +- An alert rule that is stored in `Test-Folder` and queries the two data sources. + The following request creates a custom role that includes permissions to access the alert rule: + +``` +curl --location --request POST '/api/access-control/roles/' \ +--header 'Authorization: Basic YWRtaW46cGFzc3dvcmQ=' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "version": 1, + "name": "custom:alerts.reader.in.folder.123", + "displayName": "Read-only access to alerts in folder Test-Folder", + "description": "Let user query DS1 and DS2, and read alerts in folder Test-Folders", + "group":"Custom", + "global": true, + "permissions": [ + { + "action": "folders:read", + "scope": "folders:id:92" + }, + { + "action": "alert.rules:read", + "scope": "folders:id:92" + }, + { + "action": "datasources:query", + "scope": "datasources:uid:_oAfGYUnk" + }, + { + "action": "datasources:query", + "scope": "datasources:uid:YYcBGYUnk" + } + ] +}' +``` diff --git a/docs/sources/enterprise/query-caching.md b/docs/sources/enterprise/query-caching.md index b58e5c351a8..e4a33d54cb4 100644 --- a/docs/sources/enterprise/query-caching.md +++ b/docs/sources/enterprise/query-caching.md @@ -17,9 +17,9 @@ The following cache backends are available: in-memory, Redis, and Memcached. When a panel queries a cached data source, the time until this query fetches fresh data is determined by the panel's **interval.** This means that wider panels and dashboards with shorter time ranges fetch new data more frequently than narrower panels and dashboards with longer time ranges. -Interval is visible in a panel's [query options]({{< relref "../panels/reference-query-options.md" >}}). It is calculated like this: `(max data points) / time range`. Max data points are calculated based on the width of the panel. For example, a full-width panel on a dashboard with a time range of `last 7 days` will retrieve fresh data every 10 minutes. In this example, cached data for this panel will be served for up to 10 minutes before Grafana queries the data source again and returns new data. +Interval is visible in a panel's [query options]({{< relref "../panels/query-options.md" >}}). It is calculated like this: `(max data points) / time range`. Max data points are calculated based on the width of the panel. For example, a full-width panel on a dashboard with a time range of `last 7 days` will retrieve fresh data every 10 minutes. In this example, cached data for this panel will be served for up to 10 minutes before Grafana queries the data source again and returns new data. -You can make a panel retrieve fresh data more frequently by increasing the **Max data points** setting in the panel's [query options]({{< relref "../panels/reference-query-options.md" >}}). +You can make a panel retrieve fresh data more frequently by increasing the **Max data points** setting in the panel's [query options]({{< relref "../panels/query-options.md" >}}). ## Query caching benefits diff --git a/docs/sources/enterprise/saml/setup-saml-with-okta.md b/docs/sources/enterprise/saml/set-up-saml-with-okta.md similarity index 93% rename from docs/sources/enterprise/saml/setup-saml-with-okta.md rename to docs/sources/enterprise/saml/set-up-saml-with-okta.md index 6daa4655f50..4edd3de7a7b 100644 --- a/docs/sources/enterprise/saml/setup-saml-with-okta.md +++ b/docs/sources/enterprise/saml/set-up-saml-with-okta.md @@ -1,12 +1,12 @@ --- -title: 'Setup SAML authentication with Okta in Grafana' +title: 'Set up SAML authentication with Okta in Grafana' menuTitle: 'SAML authentication with Okta' -description: 'This is a guide to setup SAML authentication with Okta in Grafana' +description: 'This is a guide to set up SAML authentication with Okta in Grafana' keywords: ['grafana', 'saml', 'documentation', 'saml-auth', 'enterprise'] weight: 30 --- -# Setup SAML with Okta +# Set up SAML with Okta Grafana supports user authentication through Okta, which is useful when you want your users to access Grafana using single sign on. This guide will follow you through the steps of configuring SAML authentication in Grafana with [Okta](https://okta.com/). You need to be an admin in your Okta organization to access Admin Console and create SAML integration. You also need permissions to edit Grafana config file and restart Grafana server. @@ -15,7 +15,7 @@ Grafana supports user authentication through Okta, which is useful when you want - To configure SAML integration with Okta, create integration inside the Okta organization first. [Add integration in Okta](https://help.okta.com/en/prod/Content/Topics/Apps/apps-overview-add-apps.htm) - Ensure you have permission to administer SAML authentication. For more information about permissions, refer to [About users and permissions]({{< relref "../manage-users-and-permissions/about-users-and-permissions.md#">}}). -**To setup SAML with Okta:** +**To set up SAML with Okta:** 1. Log in to the [Okta portal](https://login.okta.com/). 1. Go to the Admin Console in your Okta organization by clicking **Admin** in the upper-right corner. If you are in the Developer Console, then click **Developer Console** in the upper-left corner and then click **Classic UI** to switch over to the Admin Console. diff --git a/docs/sources/enterprise/usage-insights/export-logs.md b/docs/sources/enterprise/usage-insights/export-logs.md index 418d3b2134b..e317b1e5764 100644 --- a/docs/sources/enterprise/usage-insights/export-logs.md +++ b/docs/sources/enterprise/usage-insights/export-logs.md @@ -45,6 +45,8 @@ Logs of usage insights contain the following fields, where the fields followed b | `tokenId`\* | number | ID of the user’s authentication token. | | `username`\* | string | Name of the Grafana user that made the request. | | `userId`\* | number | ID of the Grafana user that made the request. | +| `totalQueries`\* | number | Number of queries executed for the data request. | +| `cachedQueries`\* | number | Number of fetched queries that came from the cache. | ## Configuration diff --git a/docs/sources/http_api/dashboard_permissions.md b/docs/sources/http_api/dashboard_permissions.md index 3496b90390d..ae159309870 100644 --- a/docs/sources/http_api/dashboard_permissions.md +++ b/docs/sources/http_api/dashboard_permissions.md @@ -19,6 +19,136 @@ The permission levels for the permission field: ## Get permissions for a dashboard +`GET /api/dashboards/uid/:uid/permissions` + +Gets all existing permissions for the dashboard with the given `uid`. + +**Example request**: + +```http +GET /api/dashboards/uid/dHEquNzGz/permissions HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response** + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 551 + +[ + { + "id": 1, + "dashboardId": -1, + "created": "2017-06-20T02:00:00+02:00", + "updated": "2017-06-20T02:00:00+02:00", + "userId": 0, + "userLogin": "", + "userEmail": "", + "teamId": 0, + "team": "", + "role": "Viewer", + "permission": 1, + "permissionName": "View", + "uid": "dHEquNzGz", + "title": "", + "slug": "", + "isFolder": false, + "url": "" + }, + { + "id": 2, + "dashboardId": -1, + "created": "2017-06-20T02:00:00+02:00", + "updated": "2017-06-20T02:00:00+02:00", + "userId": 0, + "userLogin": "", + "userEmail": "", + "teamId": 0, + "team": "", + "role": "Editor", + "permission": 2, + "permissionName": "Edit", + "uid": "dHEquNzGz", + "title": "", + "slug": "", + "isFolder": false, + "url": "" + } +] +``` + +Status Codes: + +- **200** - Ok +- **401** - Unauthorized +- **403** - Access denied +- **404** - Dashboard not found + +## Update permissions for a dashboard + +`POST /api/dashboards/uid/:uid/permissions` + +Updates permissions for a dashboard. This operation will remove existing permissions if they're not included in the request. + +**Example request**: + +```http +POST /api/dashboards/uid/dHEquNzGz/permissions +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "items": [ + { + "role": "Viewer", + "permission": 1 + }, + { + "role": "Editor", + "permission": 2 + }, + { + "teamId": 1, + "permission": 1 + }, + { + "userId": 11, + "permission": 4 + } + ] +} +``` + +JSON body schema: + +- **items** - The permission items to add/update. Items that are omitted from the list will be removed. + +**Example response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 35 + +{"message":"Dashboard permissions updated"} +``` + +Status Codes: + +- **200** - Ok +- **401** - Unauthorized +- **403** - Access denied +- **404** - Dashboard not found + +## Get permissions for a dashboard by id + +> **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new dashboard permissions API](#get-permissions-for-a-dashboard). + `GET /api/dashboards/id/:dashboardId/permissions` Gets all existing permissions for the dashboard with the given `dashboardId`. @@ -88,7 +218,9 @@ Status Codes: - **403** - Access denied - **404** - Dashboard not found -## Update permissions for a dashboard +## Update permissions for a dashboard by id + +> **Warning:** This API is deprecated since Grafana v9.0.0 and will be removed in a future release. Refer to the [new dashboard permissions API](#update-permissions-for-a-dashboard). `POST /api/dashboards/id/:dashboardId/permissions` diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index 7dd79d49e13..dba0384440b 100755 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -365,7 +365,7 @@ ORDER BY time There are two possible workarounds to resolve this problem: 1. In Grafana v8.0.3, use an alias of the string column selected as `metric`. for example, `hostname as metric`. -2. Use the [Standard field options/Display name]({{< relref "../panels/reference-standard-field-definitions.md#display-name" >}}) to format the alias. For the preceding example query, you would use `${__field.labels.hostname}` option. +2. Use the [Standard field definitions' display name]({{< relref "../panels/standard-field-definitions.md#display-name" >}}) to format the alias. For the preceding example query, you would use `${__field.labels.hostname}` option. For more information, refer to the our relational databases documentation of [Postgres]({{< relref "../datasources/postgres.md#time-series-queries" >}}), [MySQL]({{< relref "../datasources/mysql.md#time-series-queries" >}}), [Microsoft SQL Server]({{< relref "../datasources/mssql.md#time-series-queries" >}}). diff --git a/docs/sources/panels/reference-calculation-types.md b/docs/sources/panels/calculation-types.md similarity index 91% rename from docs/sources/panels/reference-calculation-types.md rename to docs/sources/panels/calculation-types.md index f341a739c25..4e586af014e 100644 --- a/docs/sources/panels/reference-calculation-types.md +++ b/docs/sources/panels/calculation-types.md @@ -1,10 +1,10 @@ +++ -title = "Reference: Calculation types" -aliases = ["/docs/grafana/latest/panels/calculations-list/", "/docs/sources/panels/reference-calculation-types/"] +title = "Calculation types" +aliases = ["/docs/grafana/latest/panels/calculations-list/", "/docs/sources/panels/reference-calculation-types/", "/docs/grafana/latest/panels/reference-calculation-types"] weight = 1100 +++ -# Reference: Calculations +# Reference: Calculation types Refer to the following list of calculations you can perform in Grafana. You can find these calculations in the **Transform** tab and in the bar gauge, gauge, and stat visualizations. diff --git a/docs/sources/panels/configure-thresholds/_index.md b/docs/sources/panels/configure-thresholds/_index.md new file mode 100644 index 00000000000..65ff85242f0 --- /dev/null +++ b/docs/sources/panels/configure-thresholds/_index.md @@ -0,0 +1,90 @@ +--- +title: 'Configure visualization thresholds' +menuTitle: 'Configure visualization thresholds' +description: 'This section includes information about using thresholds in your visualizations.' +weight: 300 +aliases: + [ + docs/grafana/latest/panels/thresholds/, + docs/grafana/latest/panels/, + docs/grafana/latest/panels/specify-thresholds/about-thresholds/, + docs/grafana/latest/panels/specify-thresholds/add-a-threshold/, + docs/grafana/latest/panels/specify-thresholds/add-threshold-to-graph/, + docs/grafana/latest/panels/specify-thresholds/delete-a-threshold/, + ] +--- + +# Configure visualization thresholds + +This section includes information about using thresholds in your visualizations. You'll learn about thresholds and their defaults, how to add or delete a threshold, and adding a threshold to a legacy panel. + +## About thresholds + +A threshold is a value that you specify for a metric that is visually reflected in a dashboard when the threshold value is met or exceeded. + +Thresholds provide one method for you to conditionally style and color your visualizations based on query results. You can apply thresholds to most, but not all, visualizations. For more information about visualizations, refer to [Visualization panels]({{< relref "../../visualizations" >}}). + +You can use thresholds to: + +- Color grid lines or grid ares areas in the [Time-series visualization]({{< relref "../../visualizations/time-series" >}}) +- Color lines in the [Time-series visualization]({{< relref "../../visualizations/time-series/graph-color-scheme/#from-thresholds" >}}) +- Color the background or value text in the [Stat visualization]({{< relref "../../visualizations/stat-panel" >}}) +- Color the gauge and threshold markers in the [Gauge visualization]({{< relref "../../visualizations/gauge-panel" >}}) +- Color markers in the [Geomap visualization]({{< relref "../../visualizations/geomap" >}}) +- Color cell text or background in the [Table visualization]({{< relref "../../visualizations/table" >}}) +- Define regions and region colors in the [State timeline visualization]({{< relref "../../visualizations/state-timeline" >}}) + +There are two types of thresholds: + +- **Absolute** thresholds are defined by a number. For example, 80 on a scale of 1 to 150. +- **Percentage** thresholds are defined relative to minimum or maximum. For example, 80 percent. + +### Default thresholds + +On visualizations that support it, Grafana sets default threshold values of: + +- 80 = red +- Base = green +- Mode = Absolute + +The **Base** value represents minus infinity. It is generally the “good” color. + +## Add or delete a threshold + +You can add as many thresholds to a panel as you want. Grafana automatically sorts thresholds values from highest to lowest. + +Delete a threshold when it is no longer relevant to your business operations. When you delete a threshold, the system removes the threshold from all visualizations that include the threshold. + +1. To add a threshold: + + a. Edit the panel to which you want to add a threshold. + + b. In the options side pane, locate the **Thresholds** section and click **+ Add threshold**. + + c. Select a threshold color, number, and mode. + Threshold mode applies to all thresholds on this panel. + + d. For a time-series panel, select a **Show thresholds** option. + +1. To delete a threshold, navigate to the panel that contains the threshold and click the trash icon next to the threshold you want to remove. + +## Add a threshold to a legacy graph panel + +In the Graph panel visualization, thresholds enable you to add lines or sections to a graph to make it easier to recognize when the graph crosses a threshold. + +1. Navigate to the graph panel to which you want to add a threshold. +1. On the **Panel** tab, click **Thresholds**. +1. Click **Add threshold**. +1. Complete the following fields: + - **T1 -** Both values are required to display a threshold. + - **lt** or **gt** - Select **lt** for less than or **gt** for greater than to indicate what the threshold applies to. + - **Value -** Enter a threshold value. Grafana draws a threshold line along the Y-axis at that value. + - **Color -** Choose a condition that corresponds to a color, or define your own color. + - **custom -** You define the fill color and line color. + - **critical -** Fill and line color are red. + - **warning -** Fill and line color are yellow. + - **ok -** Fill and line color are green. + - **Fill -** Controls whether the threshold fill is displayed. + - **Line -** Controls whether the threshold line is displayed. + - **Y-Axis -** Choose **left** or **right**. +1. Click **Save** to save the changes in the dashboard. diff --git a/docs/sources/panels/query-a-data-source/add-a-query.md b/docs/sources/panels/query-a-data-source/add-a-query.md index bf67f641fa5..32933cc183e 100644 --- a/docs/sources/panels/query-a-data-source/add-a-query.md +++ b/docs/sources/panels/query-a-data-source/add-a-query.md @@ -21,10 +21,9 @@ A query returns data that Grafana visualizes in dashboards. When you create a pa 1. Click the **Data source** drop-down menu and select a data source. 1. Click **Query options** to configure the maximum number of data points returned by the query and how frequently you want the query to request data from the data source. - For more information about query options, refer to [Reference: Query options]({{< relref "../reference-query-options.md" >}}). + For more information about query options, refer to [Query options]({{< relref "../query-options.md" >}}). 1. Write the query. - 1. Click **Apply**. The system queries the data source and presents the data in the visualization. diff --git a/docs/sources/panels/reference-query-options.md b/docs/sources/panels/query-options.md similarity index 96% rename from docs/sources/panels/reference-query-options.md rename to docs/sources/panels/query-options.md index e7dcc60c98a..a20eb53ced5 100644 --- a/docs/sources/panels/reference-query-options.md +++ b/docs/sources/panels/query-options.md @@ -1,6 +1,6 @@ +++ -title = "Reference: Query options" -aliases = ["/docs/sources/panels/reference-query-options/"] +title = "Query options" +aliases = ["/docs/sources/panels/reference-query-options/", "/docs/grafana/latest/panels/reference-query-options"] weight = 900 +++ diff --git a/docs/sources/panels/specify-thresholds/_index.md b/docs/sources/panels/specify-thresholds/_index.md deleted file mode 100644 index 7c879fc2116..00000000000 --- a/docs/sources/panels/specify-thresholds/_index.md +++ /dev/null @@ -1,11 +0,0 @@ -+++ -title = "Modify visualization text and background colors" -aliases = ["/docs/grafana/next/panels/thresholds/", "/docs/sources/panels/specify-thresholds/"] -weight = 300 -+++ - -# Modify visualization text and background colors - -Use thresholds to set the color of a visualization text and background. - -{{< section >}} diff --git a/docs/sources/panels/specify-thresholds/about-thresholds.md b/docs/sources/panels/specify-thresholds/about-thresholds.md deleted file mode 100644 index c99ae25c479..00000000000 --- a/docs/sources/panels/specify-thresholds/about-thresholds.md +++ /dev/null @@ -1,26 +0,0 @@ -+++ -title = "About thresholds" -weight = 10 -aliases = ["/docs/sources/panels/specify-thresholds/about-thresholds/"] -+++ - -# About thresholds - -Thresholds set the color of either the value text or the background based on conditions that you define. - -There are two types of thresholds: - -- **Absolute** thresholds are defined based on a number. For example, 80 on a scale of 1 to 150. -- **Percentage** thresholds are defined relative to minimum or maximum. For example, 80 percent. - -You can apply thresholds to most, but not all, visualizations. - -## Default thresholds - -On visualizations that support it, Grafana sets default threshold values of: - -- 80 = red -- Base = green -- Mode = Absolute - -The **Base** value represents minus infinity. It is generally the “good” color. diff --git a/docs/sources/panels/specify-thresholds/add-a-threshold.md b/docs/sources/panels/specify-thresholds/add-a-threshold.md deleted file mode 100644 index 27ce198f93a..00000000000 --- a/docs/sources/panels/specify-thresholds/add-a-threshold.md +++ /dev/null @@ -1,29 +0,0 @@ -+++ -title = "Add a threshold" -weight = 20 -aliases = ["/docs/sources/panels/specify-thresholds/add-a-threshold/"] -+++ - -# Add a threshold - -You can add as many thresholds to a panel as you want. Grafana automatically sorts thresholds from highest value to lowest. - -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel.md" >}}). - -**To add a threshold**: - -1. Edit the panel to which you want to add a threshold. -1. On the panel display options, locate the **Thresholds** section. -1. Click **+ Add threshold**. - - Grafana adds a threshold value and color. - -1. Accept the recommendations or edit the threshold. - - **Edit color:** To select a color, click the color dot. - - **Edit number:** To change the threshold value, click in the field and enter a number. -1. Select a **Threshold mode**. - Threshold mode applies to all thresholds on this panel. -1. In the **Show thresholds** drop-down, select a threshold display option. -1. Click **Save**. diff --git a/docs/sources/panels/specify-thresholds/add-threshold-to-graph.md b/docs/sources/panels/specify-thresholds/add-threshold-to-graph.md deleted file mode 100644 index edc6f2044a0..00000000000 --- a/docs/sources/panels/specify-thresholds/add-threshold-to-graph.md +++ /dev/null @@ -1,32 +0,0 @@ -+++ -title = "Add a threshold to a legacy graph panel" -weight = 40 -aliases = ["/docs/sources/panels/specify-thresholds/add-threshold-to-graph/"] -+++ - -# Add a threshold to a legacy graph panel - -In the Graph panel visualization, thresholds enable you to add lines or sections to a graph to make it easier to recognize when the graph crosses a threshold. - -## Before you begin - -- [Add a panel to a dashboard]({{< relref "../working-with-panels/add-panel.md" >}}). - -**To add a threshold to a graph panel**: - -1. Navigate to the graph panel to which you want to add a threshold. -1. On the **Panel** tab, click **Thresholds**. -1. Click **Add threshold**. -1. Complete the following fields: - - **T1 -** Both values are required to display a threshold. - - **lt** or **gt** - Select **lt** for less than or **gt** for greater than to indicate what the threshold applies to. - - **Value -** Enter a threshold value. Grafana draws a threshold line along the Y-axis at that value. - - **Color -** Choose a condition that corresponds to a color, or define your own color. - - **custom -** You define the fill color and line color. - - **critical -** Fill and line color are red. - - **warning -** Fill and line color are yellow. - - **ok -** Fill and line color are green. - - **Fill -** Controls whether the threshold fill is displayed. - - **Line -** Controls whether the threshold line is displayed. - - **Y-Axis -** Choose **left** or **right**. -1. Click **Save** to save the changes in the dashboard. diff --git a/docs/sources/panels/specify-thresholds/delete-a-threshold.md b/docs/sources/panels/specify-thresholds/delete-a-threshold.md deleted file mode 100644 index c673e211fc6..00000000000 --- a/docs/sources/panels/specify-thresholds/delete-a-threshold.md +++ /dev/null @@ -1,20 +0,0 @@ -+++ -title = "Delete a threshold" -weight = 30 -aliases = ["/docs/sources/panels/specify-thresholds/delete-a-threshold/"] -+++ - -# Delete a threshold - -Delete a threshold when it is no longer relevant to your business operations. When you delete a threshold, the system removes the threshold from all visualizations that include the threshold. - -## Before you begin - -- [Add a threshold]({{< relref "./add-a-threshold.md" >}}). - -**To delete a threshold**: - -1. Navigate to the panel to which you want to add a threshold. -1. Click the **Field** tab. (Or **Panel** tab for a graph panel.) -1. Click the trash can icon next to the threshold you want to remove. -1. Click **Save** to save the changes in the dashboard. diff --git a/docs/sources/panels/reference-standard-field-definitions.md b/docs/sources/panels/standard-field-definitions.md similarity index 98% rename from docs/sources/panels/reference-standard-field-definitions.md rename to docs/sources/panels/standard-field-definitions.md index d8815dd2749..70cb2a5dcb3 100644 --- a/docs/sources/panels/reference-standard-field-definitions.md +++ b/docs/sources/panels/standard-field-definitions.md @@ -1,6 +1,6 @@ +++ -title = "Reference: Standard field definitions" -aliases = ["/docs/sources/panels/reference-standard-field-definitions/"] +title = "Standard field definitions" +aliases = ["/docs/sources/panels/reference-standard-field-definitions/", "/docs/grafana/latest/panels/reference-standard-field-definitions"] weight = 1200 +++ diff --git a/docs/sources/panels/transform-data/about-transformation.md b/docs/sources/panels/transform-data/about-transformation.md index d357e48a19d..348aee91f59 100644 --- a/docs/sources/panels/transform-data/about-transformation.md +++ b/docs/sources/panels/transform-data/about-transformation.md @@ -21,8 +21,7 @@ You can also use the output of one transformation as the input to another transf ## Transformation types -Grafana provides a number of ways that you can transform data. For a complete list of transformations, refer to -[Reference: Transformation functions]({{< relref "../reference-transformation-functions.md" >}}). +Grafana provides a number of ways that you can transform data. For a complete list of transformations, refer to [Transformation functions]({{< relref "transformation-functions.md" >}}). ## Order of transformations diff --git a/docs/sources/panels/transform-data/add-transformation-to-data.md b/docs/sources/panels/transform-data/add-transformation-to-data.md new file mode 100644 index 00000000000..c4a3cd21dc0 --- /dev/null +++ b/docs/sources/panels/transform-data/add-transformation-to-data.md @@ -0,0 +1,30 @@ ++++ +title = "Add a transformation to data" +weight = 20 +aliases = ["/docs/sources/panels/transform-data/add-transformation-to-data/", "/docs/sources/panels/transform-data/apply-transformation-to-data", "/docs/grafana/latest/panels/transform-data/apply-transformation-to-data"] ++++ + +# Add a transformation function to data + +The following steps guide you in adding a transformation to data. This documentation does not include steps for each type of transformation. For a complete list of transformations, refer to [Transformation functions]({{< relref "transformation-functions.md" >}}). + +## Before you begin + +- [Add a query]({{< relref "../query-a-data-source/add-a-query.md" >}}). + +**To apply a transformation function to data**: + +1. Navigate to the panel where you want to add one or more transformations. +1. Click the panel title and then click **Edit**. +1. Click the **Transform** tab. +1. Click a transformation. + + A transformation row appears where you configure the transformation options. For more information about how to configure a transformation, refer to [Transformation functions]({{< relref "transformation-functions.md" >}}). + + For information about available calculations, refer to [Calculation types]({{< relref "../calculation-types.md" >}}). + +1. To apply another transformation, click **Add transformation**. + + This transformation acts on the result set returned by the previous transformation. + + {{< figure src="/static/img/docs/transformations/transformations-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}} diff --git a/docs/sources/panels/transform-data/apply-transformation-to-data.md b/docs/sources/panels/transform-data/apply-transformation-to-data.md deleted file mode 100644 index 4161d0ff403..00000000000 --- a/docs/sources/panels/transform-data/apply-transformation-to-data.md +++ /dev/null @@ -1,30 +0,0 @@ -+++ -title = "Apply a transformation to data" -weight = 20 -aliases = ["/docs/sources/panels/transform-data/apply-transformation-to-data/"] -+++ - -# Apply a transformation function to data - -The following steps guide you in applying a transformation to data. This documentation does not include steps for each type of transformation. For a complete list of transformations, refer to [Reference: Transformation functions]({{< relref "../reference-transformation-functions.md" >}}). - -## Before you begin - -- [Add a query]({{< relref "../query-a-data-source/add-a-query.md" >}}). - -**To apply a transformation function to data**: - -1. Navigate to the panel where you want to add one or more transformations. -1. Click the panel title and then click **Edit**. -1. Click the **Transform** tab. -1. Click a transformation. - - A transformation row appear where you configure the transformation options. For more information about how to configure a transformation, refer to [Reference: Transformation functions]({{< relref "../reference-transformation-functions.md" >}}). - - For information about available calculations, refer to [Reference: Calculations]({{< relref "../reference-calculation-types.md" >}}). - -1. To apply another transformation, click **Add transformation**. - - This transformation acts on the result set returned by the previous transformation. - - {{< figure src="/static/img/docs/transformations/transformations-7-0.png" class="docs-image--no-shadow" max-width= "1100px" >}} diff --git a/docs/sources/panels/reference-transformation-functions.md b/docs/sources/panels/transform-data/transformation-functions.md similarity index 98% rename from docs/sources/panels/reference-transformation-functions.md rename to docs/sources/panels/transform-data/transformation-functions.md index 3e66493f107..180d73a004b 100644 --- a/docs/sources/panels/reference-transformation-functions.md +++ b/docs/sources/panels/transform-data/transformation-functions.md @@ -1,6 +1,6 @@ +++ -title = "Reference: Transformation functions" -aliases = ["/docs/grafana/latest/panels/transformations/types-options/", "docs/sources/panels/reference-transformation-functions/"] +title = "Transformation functions" +aliases = ["/docs/grafana/latest/panels/transformations/types-options/", "/docs/sources/panels/reference-transformation-functions/", "/docs/grafana/latest/panels/reference-transformation-functions"] weight = 1000 +++ @@ -16,7 +16,7 @@ Use this transformation to add a new field calculated from two other fields. Eac - **Reduce row -** Apply selected calculation on each row of selected fields independently. - **Binary option -** Apply basic math operation(sum, multiply, etc) on values in a single row from two selected fields. - **Field name -** Select the names of fields you want to use in the calculation for the new field. -- **Calculation -** If you select **Reduce row** mode, then the **Calculation** field appears. Click in the field to see a list of calculation choices you can use to create the new field. For information about available calculations, refer to [Reference: Calculations]({{< relref "./reference-calculation-types.md" >}}). +- **Calculation -** If you select **Reduce row** mode, then the **Calculation** field appears. Click in the field to see a list of calculation choices you can use to create the new field. For information about available calculations, refer to [Calculation types]({{< relref "../calculation-types.md" >}}). - **Operation -** If you select **Binary option** mode, then the **Operation** fields appear. These fields allow you to do basic math operations on values in a single row from two selected fields. You can also use numerical values for binary operations. - **Alias -** (Optional) Enter the name of your new field. If you leave this blank, then the field will be named to match the calculation. - **Replace all fields -** (Optional) Select this option if you want to hide all other fields and display only your calculated field in the visualization. @@ -189,7 +189,7 @@ Conditions that are invalid or incompletely configured are ignored. ## Group by -This transformation groups the data by a specified field (column) value and processes calculations on each group. Click to see a list of calculation choices. For information about available calculations, refer to the [List of calculations]({{< relref "./reference-calculation-types.md" >}}). +This transformation groups the data by a specified field (column) value and processes calculations on each group. Click to see a list of calculation choices. For information about available calculations, refer to [Calculation types]({{< relref "../calculation-types.md" >}}). Here's an example of original data. @@ -323,7 +323,7 @@ After merge: ## Merge -Use this transformation to combine the result from multiple queries into one single result. This is helpful when using the table panel visualization. Values that can be merged are combined into the same row. Values are mergeable if the shared fields contain the same data. For information, refer to [Table panel]({{< relref "../visualizations/table/_index.md" >}}). +Use this transformation to combine the result from multiple queries into one single result. This is helpful when using the table panel visualization. Values that can be merged are combined into the same row. Values are mergeable if the shared fields contain the same data. For information, refer to [Table panel]({{< relref "../../visualizations/table/_index.md" >}}). In the example below, we have two queries returning table data. It is visualized as two separate tables before applying the transformation. @@ -493,7 +493,7 @@ As you can see each row in the source data becomes a separate field. Each field > **Note:** This transformation is available in Grafana 7.5.10+ and Grafana 8.0.6+. -Prepare time series transformation is useful when a data source returns time series data in a format that isn't supported by the panel you want to use. For more information about data frame formats, refer to [Data frames]({{< relref "../developers/plugins/data-frames.md" >}}). +Prepare time series transformation is useful when a data source returns time series data in a format that isn't supported by the panel you want to use. For more information about data frame formats, refer to [Data frames]({{< relref "../../developers/plugins/data-frames.md" >}}). This transformation helps you resolve this issue by converting the time series data from either the wide format to the long format or the other way around. diff --git a/docs/sources/panels/working-with-panels/add-panel.md b/docs/sources/panels/working-with-panels/add-panel.md index b35c960887c..7a09be1b9eb 100644 --- a/docs/sources/panels/working-with-panels/add-panel.md +++ b/docs/sources/panels/working-with-panels/add-panel.md @@ -47,7 +47,7 @@ Panels allow you to show your data in visual form. Each panel needs at least one - [Format data using value mapping]({{< relref "../format-data/about-value-mapping.md" >}}) - [Visualization-specific options]({{< relref "../../visualizations/_index.md" >}}) - [Override field values]({{< relref "../override-field-values/about-field-overrides.md" >}}) - - [Specify thresholds to set the color of visualization text and background]({{< relref "../specify-thresholds/about-thresholds.md" >}}) + - [Configure thresholds]({{< relref "../configure-thresholds/" >}}) - [Apply color to series and fields]({{< relref "./apply-color-to-series.md" >}}) 1. Add a note to describe the visualization (or describe your changes) and then click **Save** in the upper-right corner of the page. diff --git a/docs/sources/panels/working-with-panels/configure-legend.md b/docs/sources/panels/working-with-panels/configure-legend.md index 6c97260c7ee..07b32912f43 100644 --- a/docs/sources/panels/working-with-panels/configure-legend.md +++ b/docs/sources/panels/working-with-panels/configure-legend.md @@ -83,7 +83,7 @@ By default, Grafana specifies the color of your series data, which you can chang ## Sort series -Change legend mode to **Table** and choose [calculations]({{< relref "../reference-calculation-types.md" >}}) to be displayed in the legend. Click the calculation name header in the legend table to sort the values in the table in ascending or descending order. +Change legend mode to **Table** and choose [calculations]({{< relref "../calculation-types.md" >}}) to be displayed in the legend. Click the calculation name header in the legend table to sort the values in the table in ascending or descending order. The sort order affects the positions of the bars in the Bar chart panel as well as the order of stacked series in the Time series and Bar chart panels. diff --git a/docs/sources/panels/working-with-panels/format-standard-fields.md b/docs/sources/panels/working-with-panels/format-standard-fields.md index 1a70f7115fc..47334a01b89 100644 --- a/docs/sources/panels/working-with-panels/format-standard-fields.md +++ b/docs/sources/panels/working-with-panels/format-standard-fields.md @@ -10,7 +10,7 @@ The data model used in Grafana, namely the [data frame]({{< relref "../../develo Field options allow you to change how the data is displayed in your visualizations. Options and overrides that you apply do not change the data, they change how Grafana displays the data. When you change an option, it is applied to all fields, meaning all series or columns. For example, if you change the unit to percentage, then all fields with numeric values are displayed in percentages. -For a complete list of field formatting options, refer to [Reference: Standard field definitions]({{< relref "../reference-standard-field-definitions.md" >}}). +For a complete list of field formatting options, refer to [Standard field definitions]({{< relref "../standard-field-definitions.md" >}}). > You can apply standard options to most built-in Grafana panels. Some older panels and community panels that have not updated to the new panel and data model will be missing either all or some of these field options. @@ -26,6 +26,6 @@ For a complete list of field formatting options, refer to [Reference: Standard f 1. Select the standard options you want to apply. - For more information about standard options, refer to [Reference: Standard field definitions]({{< relref "../reference-standard-field-definitions/index.md" >}}). + For more information about standard options, refer to [Standard field definitions]({{< relref "../standard-field-definitions/index.md" >}}). 1. To preview your change, click outside of the field option box you are editing or press **Enter**. diff --git a/docs/sources/release-notes/_index.md b/docs/sources/release-notes/_index.md index df1de760538..88394d775f2 100644 --- a/docs/sources/release-notes/_index.md +++ b/docs/sources/release-notes/_index.md @@ -8,7 +8,9 @@ weight = 10000 Here you can find detailed release notes that list everything that is included in every release as well as notices about deprecations, breaking changes as well as changes that relate to plugin development. +- [Release notes for 8.5.0]({{< relref "release-notes-8-5-0" >}}) - [Release notes for 8.5.0-beta1]({{< relref "release-notes-8-5-0-beta1" >}}) +- [Release notes for 8.4.7]({{< relref "release-notes-8-4-7" >}}) - [Release notes for 8.4.6]({{< relref "release-notes-8-4-6" >}}) - [Release notes for 8.4.5]({{< relref "release-notes-8-4-5" >}}) - [Release notes for 8.4.4]({{< relref "release-notes-8-4-4" >}}) diff --git a/docs/sources/release-notes/release-notes-8-4-7.md b/docs/sources/release-notes/release-notes-8-4-7.md new file mode 100644 index 00000000000..671bdee6d2c --- /dev/null +++ b/docs/sources/release-notes/release-notes-8-4-7.md @@ -0,0 +1,19 @@ ++++ +title = "Release notes for Grafana 8.4.7" +hide_menu = true ++++ + + + +# Release notes for Grafana 8.4.7 + +### Features and enhancements + +- **CloudWatch:** Added missing MemoryDB Namespace metrics. [#47290](https://github.com/grafana/grafana/pull/47290), [@james-deee](https://github.com/james-deee) +- **Histogram Panel:** Take decimal into consideration. [#47330](https://github.com/grafana/grafana/pull/47330), [@mdvictor](https://github.com/mdvictor) +- **TimeSeries:** Sort tooltip values based on raw values. [#46738](https://github.com/grafana/grafana/pull/46738), [@dprokop](https://github.com/dprokop) + +### Bug fixes + +- **API:** Include userId, orgId, uname in request logging middleware. [#47183](https://github.com/grafana/grafana/pull/47183), [@marefr](https://github.com/marefr) +- **Elasticsearch:** Respect maxConcurrentShardRequests datasource setting. [#47120](https://github.com/grafana/grafana/pull/47120), [@alexandrst88](https://github.com/alexandrst88) diff --git a/docs/sources/release-notes/release-notes-8-5-0.md b/docs/sources/release-notes/release-notes-8-5-0.md new file mode 100644 index 00000000000..cc06d890fb1 --- /dev/null +++ b/docs/sources/release-notes/release-notes-8-5-0.md @@ -0,0 +1,81 @@ ++++ +title = "Release notes for Grafana 8.5.0" +hide_menu = true ++++ + + + +# Release notes for Grafana 8.5.0 + +### Features and enhancements + +- **Alerting:** Add contact points provisioning API. [#47197](https://github.com/grafana/grafana/pull/47197), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) +- **Alerting:** Add resolved count to notification title when both firing and resolved present. [#46697](https://github.com/grafana/grafana/pull/46697), [@JacobsonMT](https://github.com/JacobsonMT) +- **Alerting:** Alert rule should wait For duration when execution error state is Alerting. [#47052](https://github.com/grafana/grafana/pull/47052), [@grobinson-grafana](https://github.com/grobinson-grafana) +- **Alerting:** Classic conditions can now display multiple values. [#46971](https://github.com/grafana/grafana/pull/46971), [@gotjosh](https://github.com/gotjosh) +- **Alerting:** Display query from grafana-managed alert rules on `/api/v1/rules`. [#45969](https://github.com/grafana/grafana/pull/45969), [@gotjosh](https://github.com/gotjosh) +- **Alerting:** Enhance support for arbitrary group names in managed alerts. [#47785](https://github.com/grafana/grafana/pull/47785), [@gillesdemey](https://github.com/gillesdemey) +- **Alerting:** add field for custom slack endpoint. [#45751](https://github.com/grafana/grafana/pull/45751), [@nathanrodman](https://github.com/nathanrodman) +- **Azure Monitor :** Adding json formatting of error messages in Panel Header Corner and Inspect Error Tab. [#44877](https://github.com/grafana/grafana/pull/44877), [@yaelleC](https://github.com/yaelleC) +- **Azure Monitor:** Add 2 more Curated Dashboards for VM Insights. [#45187](https://github.com/grafana/grafana/pull/45187), [@jcolladokuri](https://github.com/jcolladokuri) +- **CloudWatch:** Handle new error codes for MetricInsights. [#47033](https://github.com/grafana/grafana/pull/47033), [@Gabrielopesantos](https://github.com/Gabrielopesantos) +- **Dashboards:** show changes in save dialog. [#46557](https://github.com/grafana/grafana/pull/46557), [@ryantxu](https://github.com/ryantxu) +- **DataSource:** Default data source is no longer a persisted state but just the default data source for new panels. [#45132](https://github.com/grafana/grafana/pull/45132), [@torkelo](https://github.com/torkelo) +- **DataSourcePlugin API:** Allow queries import when changing data source type. [#47435](https://github.com/grafana/grafana/pull/47435), [@dprokop](https://github.com/dprokop) +- **Explore:** Remove return to panel button. [#45018](https://github.com/grafana/grafana/pull/45018), [@gelicia](https://github.com/gelicia) +- **Explore:** allow users to save Explore state to a new panel in a new dashboard. [#45148](https://github.com/grafana/grafana/pull/45148), [@Elfo404](https://github.com/Elfo404) +- **Instrumentation:** Proxy status code correction and various improvements. [#47473](https://github.com/grafana/grafana/pull/47473), [@marefr](https://github.com/marefr) +- **Logging:** Introduce feature toggle to activate gokit/log format. [#47336](https://github.com/grafana/grafana/pull/47336), [@ying-jeanne](https://github.com/ying-jeanne) +- **NewsPanel:** Add support for Atom feeds. [#45390](https://github.com/grafana/grafana/pull/45390), [@kaydelaney](https://github.com/kaydelaney) +- **Plugins:** Add deprecation notice for /api/tsdb/query endpoint. [#45238](https://github.com/grafana/grafana/pull/45238), [@wbrowne](https://github.com/wbrowne) +- **Plugins:** Adding support for traceID field to accept variables. [#45559](https://github.com/grafana/grafana/pull/45559), [@vinisdl](https://github.com/vinisdl) +- **PostgreSQL:** \_\_unixEpochGroup to support arithmetic expression as argument. [#46764](https://github.com/grafana/grafana/pull/46764), [@s0nik42](https://github.com/s0nik42) +- **Profile/Help:** Expose option to disable profile section and help menu. [#46308](https://github.com/grafana/grafana/pull/46308), [@cameronwaterman](https://github.com/cameronwaterman) +- **Prometheus:** Enable new visual query builder by default. [#46634](https://github.com/grafana/grafana/pull/46634), [@torkelo](https://github.com/torkelo) +- **SAML:** Allow disabling of SAML signups. [#47481](https://github.com/grafana/grafana/pull/47481), [@mmandrus](https://github.com/mmandrus) +- **SAML:** Allow disabling of SAML signups. (Enterprise) +- **Table:** New pagination option. [#45732](https://github.com/grafana/grafana/pull/45732), [@zoltanbedi](https://github.com/zoltanbedi) +- **TablePanel:** Add cell inspect option. [#45620](https://github.com/grafana/grafana/pull/45620), [@dprokop](https://github.com/dprokop) +- **Tempo / Trace Viewer:** Support Span Links in Trace Viewer. [#45632](https://github.com/grafana/grafana/pull/45632), [@Shachi16](https://github.com/Shachi16) +- **Tempo:** Download span references in data inspector. [#47074](https://github.com/grafana/grafana/pull/47074), [@connorlindsey](https://github.com/connorlindsey) +- **Tempo:** Separate trace to logs and loki search datasource config. [#46655](https://github.com/grafana/grafana/pull/46655), [@connorlindsey](https://github.com/connorlindsey) +- **Trace View:** Show number of child spans. [#44393](https://github.com/grafana/grafana/pull/44393), [@tharun208](https://github.com/tharun208) +- **Transformations:** Support escaped characters in key-value pair parsing. [#47901](https://github.com/grafana/grafana/pull/47901), [@aangelisc](https://github.com/aangelisc) + +### Bug fixes + +- **Azure Monitor:** Bug Fix for incorrect variable cascading for template variables. [#47478](https://github.com/grafana/grafana/pull/47478), [@jcolladokuri](https://github.com/jcolladokuri) +- **CloudWatch:** List all metrics properly in SQL autocomplete. [#45898](https://github.com/grafana/grafana/pull/45898), [@sunker](https://github.com/sunker) +- **CloudWatch:** Run query on blur in logs query field. [#47454](https://github.com/grafana/grafana/pull/47454), [@fridgepoet](https://github.com/fridgepoet) +- **Dashboard:** Template variables are now correctly persisted when clicking breadcrumb links. [#46790](https://github.com/grafana/grafana/pull/46790), [@ashharrison90](https://github.com/ashharrison90) +- **DashboardPage:** Remember scroll position when coming back panel edit / view panel. [#47639](https://github.com/grafana/grafana/pull/47639), [@torkelo](https://github.com/torkelo) +- **Panel Edit:** Options search now works correctly when a logarithmic scale option is set. [#47927](https://github.com/grafana/grafana/pull/47927), [@ashharrison90](https://github.com/ashharrison90) +- **Postgres:** Return tables with hyphenated schemes. [#45754](https://github.com/grafana/grafana/pull/45754), [@zuchka](https://github.com/zuchka) +- **Table panel:** Fix horizontal scrolling when pagination is enabled. [#47776](https://github.com/grafana/grafana/pull/47776), [@dprokop](https://github.com/dprokop) +- **Variables:** Ensure variables in query params are correctly recognised. [#47049](https://github.com/grafana/grafana/pull/47049), [@ashharrison90](https://github.com/ashharrison90) +- **Variables:** Fix crash when changing query variable datasource. [#44957](https://github.com/grafana/grafana/pull/44957), [@joshhunt](https://github.com/joshhunt) +- **Visualizations:** Stack negative-valued series downwards. [#47373](https://github.com/grafana/grafana/pull/47373), [@leeoniya](https://github.com/leeoniya) + +### Breaking changes + +For a proxied request, e.g. Grafana's datasource or plugin proxy: + +- If the request is cancelled, e.g. from the browser/by the client, the HTTP status code is now `499 Client closed request` instead of `502 Bad gateway` +- If the request times out, e.g. takes longer time than allowed, the HTTP status code is now `504 Gateway timeout` instead of `502 Bad gateway`. Issue [#47473](https://github.com/grafana/grafana/issues/47473) + +The change in behavior is that negative-valued series are now stacked downwards from 0 (in their own stacks), rather than downwards from the top of the positive stacks. We now automatically group stacks by Draw style, Line interpolation, and Bar alignment, making it impossible to stack bars on top of lines, or smooth lines on top of stepped lines. Issue [#47373](https://github.com/grafana/grafana/issues/47373) + +The meaning of the default data source has now changed from being a persisted property in a panel. Before when you selected the default data source for a panel and later changed the default data source to another data source it would change all panels who were configured to use the default data source. From now on the default data source is just the default for new panels and changing the default will not impact any currently saved dashboards. Issue [#45132](https://github.com/grafana/grafana/issues/45132) + +The Tooltip component provided by `@grafana/ui` is no longer automatically interactive (that is you can hover onto it and click a link or select text). It will from now on by default close automatically when you mouse out from the trigger element. To make tooltips behave like before set the new `interactive` property to true. + Issue [#45053](https://github.com/grafana/grafana/issues/45053) + +### Deprecations + +`/api/tsdb/query` API has been deprecated and will be removed in a future release. Use [/api/ds/query](https://grafana.com/docs/grafana/latest/http_api/data_source/#query-a-data-source) instead. Issue [#45238](https://github.com/grafana/grafana/issues/45238) + +### Plugin development fixes & changes + +- **Card:** Increase clickable area when meta items are present. [#47935](https://github.com/grafana/grafana/pull/47935), [@ashharrison90](https://github.com/ashharrison90) +- **Loki:** Fix operator description propup from being shortened. [#46575](https://github.com/grafana/grafana/pull/46575), [@glintik](https://github.com/glintik) +- **Tooltips:** Make tooltips non interactive by default. [#45053](https://github.com/grafana/grafana/pull/45053), [@torkelo](https://github.com/torkelo) diff --git a/docs/sources/visualizations/bar-chart.md b/docs/sources/visualizations/bar-chart.md index 04f60103314..2285ce05879 100644 --- a/docs/sources/visualizations/bar-chart.md +++ b/docs/sources/visualizations/bar-chart.md @@ -98,7 +98,7 @@ Gradient color is generated based on the hue of the line color. ### Legend calculations -Choose which of the [standard calculations]({{< relref "../panels/reference-calculation-types.md">}}) to show in the legend. You can have more than one. +Choose which of the [standard calculations]({{< relref "../panels/calculation-types.md">}}) to show in the legend. You can have more than one. ## Text size @@ -148,4 +148,4 @@ Set a **Soft min** or **soft max** option for better control of Y-axis limits. B **Soft min** and **soft max** settings can prevent blips from turning into mountains when the data is mostly flat, and hard min or max derived from standard min and max field options can prevent intermittent spikes from flattening useful detail by clipping the spikes past a defined point. -You can set standard min/max options to define hard limits of the Y-axis. For more information, refer to [Standard field options]({{< relref "../panels/reference-standard-field-definitions.md#max" >}}). +You can set standard min/max options to define hard limits of the Y-axis. For more information, refer to [Standard field definitions]({{< relref "../panels/standard-field-definitions.md#max" >}}). diff --git a/docs/sources/visualizations/bar-gauge-panel.md b/docs/sources/visualizations/bar-gauge-panel.md index 3671ffd721e..28e0aecb1c9 100644 --- a/docs/sources/visualizations/bar-gauge-panel.md +++ b/docs/sources/visualizations/bar-gauge-panel.md @@ -26,7 +26,7 @@ Choose how Grafana displays your data. Show a calculated value based on all rows. -- **Calculation -** Select a reducer function that Grafana will use to reduce many fields to a single value. For a list of available calculations, refer to [List of calculations]({{< relref "../panels/reference-calculation-types.md" >}}). +- **Calculation -** Select a reducer function that Grafana will use to reduce many fields to a single value. For a list of available calculations, refer to [Calculation types]({{< relref "../panels/calculation-types.md" >}}). - **Fields -** Select the fields display in the panel. #### All values diff --git a/docs/sources/visualizations/gauge-panel.md b/docs/sources/visualizations/gauge-panel.md index 045d1d6ee8e..ca064af8f11 100644 --- a/docs/sources/visualizations/gauge-panel.md +++ b/docs/sources/visualizations/gauge-panel.md @@ -24,7 +24,7 @@ Choose how Grafana displays your data. Show a calculated value based on all rows. -- **Calculation -** Select a reducer function that Grafana will use to reduce many fields to a single value. For a list of available calculations, refer to [List of calculations]({{< relref "../panels/reference-calculation-types.md" >}}). +- **Calculation -** Select a reducer function that Grafana will use to reduce many fields to a single value. For a list of available calculations, refer to [Calculation types]({{< relref "../panels/calculation-types.md" >}}). - **Fields -** Select the fields display in the panel. #### All values diff --git a/docs/sources/visualizations/graph-panel.md b/docs/sources/visualizations/graph-panel.md index d2006ea5916..6832bca406e 100644 --- a/docs/sources/visualizations/graph-panel.md +++ b/docs/sources/visualizations/graph-panel.md @@ -16,9 +16,9 @@ The graph panel can render metrics as a line, a path of dots, or a series of bar Graph visualizations allow you to apply: - [Alerts]({{< relref "../alerting/_index.md" >}}) - This is the only type of visualization that allows you to set alerts. -- [Transform data]({{< relref "../panels/transform-data/apply-transformation-to-data.md" >}}) +- [Transform data]({{< relref "../panels/transform-data/add-transformation-to-data.md" >}}) - [Add a field override]({{< relref "../panels/override-field-values/add-a-field-override.md" >}}) -- [Add a threshold]({{< relref "../panels/specify-thresholds/add-a-threshold.md" >}}) +- [Configure thresholds]({{< relref "../panels/configure-thresholds/" >}}) ## Display options diff --git a/docs/sources/visualizations/histogram.md b/docs/sources/visualizations/histogram.md index 548b272c7e1..990bd65c12e 100644 --- a/docs/sources/visualizations/histogram.md +++ b/docs/sources/visualizations/histogram.md @@ -64,4 +64,4 @@ Gradient color is generated based on the hue of the line color. ### Legend calculations -Choose a [standard calculations]({{< relref "../panels/reference-calculation-types.md">}}) to show in the legend. You can select more than one. +Choose a [standard calculations]({{< relref "../panels/calculation-types.md">}}) to show in the legend. You can select more than one. diff --git a/docs/sources/visualizations/pie-chart-panel.md b/docs/sources/visualizations/pie-chart-panel.md index 2720bfb0321..f0cf68b0258 100644 --- a/docs/sources/visualizations/pie-chart-panel.md +++ b/docs/sources/visualizations/pie-chart-panel.md @@ -24,7 +24,7 @@ Choose how much information to show. ### Calculation -Select a calculation to reduce each series when Calculate has been selected. For information about available calculations, refer to the [Calculation list]({{< relref "../panels/reference-calculation-types.md" >}}). +Select a calculation to reduce each series when Calculate has been selected. For information about available calculations, refer to [Calculation types]({{< relref "../panels/calculation-types.md" >}}). ### Limit diff --git a/docs/sources/visualizations/stat-panel.md b/docs/sources/visualizations/stat-panel.md index a1608afaab0..33de02761f0 100644 --- a/docs/sources/visualizations/stat-panel.md +++ b/docs/sources/visualizations/stat-panel.md @@ -41,7 +41,7 @@ Choose how Grafana displays your data. Show a calculated value based on all rows. -- **Calculation -** Select a reducer function that Grafana will use to reduce many fields to a single value. For a list of available calculations, refer to [List of calculations]({{< relref "../panels/reference-calculation-types.md" >}}). +- **Calculation -** Select a reducer function that Grafana will use to reduce many fields to a single value. For a list of available calculations, refer to [Calculation types]({{< relref "../panels/calculation-types.md" >}}). - **Fields -** Select the fields display in the panel. #### All values diff --git a/docs/sources/visualizations/time-series/_index.md b/docs/sources/visualizations/time-series/_index.md index e0cec74145f..e9085d0ede8 100644 --- a/docs/sources/visualizations/time-series/_index.md +++ b/docs/sources/visualizations/time-series/_index.md @@ -23,7 +23,7 @@ These options are available whether you are graphing your time series as lines, ### Legend calculations -Choose which of the [standard calculations]({{< relref "../../panels/reference-calculation-types.md">}}) to show in the legend. You can have more than one. +Choose which of the [standard calculations]({{< relref "../../panels/calculation-types.md">}}) to show in the legend. You can have more than one. ## Graph styles diff --git a/docs/sources/visualizations/time-series/change-axis-display.md b/docs/sources/visualizations/time-series/change-axis-display.md index bd6244531c3..4987bf2996e 100644 --- a/docs/sources/visualizations/time-series/change-axis-display.md +++ b/docs/sources/visualizations/time-series/change-axis-display.md @@ -61,7 +61,7 @@ Set a **Soft min** or **soft max** option for better control of Y-axis limits. B **Soft min** and **soft max** settings can prevent blips from turning into mountains when the data is mostly flat, and hard min or max derived from standard min and max field options can prevent intermittent spikes from flattening useful detail by clipping the spikes past a defined point. -You can set standard min/max options to define hard limits of the Y-axis. For more information, refer to [Reference: Standard field definitions]({{< relref "../../panels/reference-standard-field-definitions.md#max" >}}). +You can set standard min/max options to define hard limits of the Y-axis. For more information, refer to [Standard field definitions]({{< relref "../../panels/standard-field-definitions.md#max" >}}). ![Label example](/static/img/docs/time-series-panel/axis-soft-min-max-7-4.png) diff --git a/docs/sources/whatsnew/_index.md b/docs/sources/whatsnew/_index.md index aa62630e764..2f0814ef448 100644 --- a/docs/sources/whatsnew/_index.md +++ b/docs/sources/whatsnew/_index.md @@ -11,6 +11,7 @@ as info on deprecations, breaking changes and plugin development read the [relea ## Grafana 8 +- [What's new in 8.5]({{< relref "whats-new-in-v8-5" >}}) - [What's new in 8.4]({{< relref "whats-new-in-v8-4" >}}) - [What's new in 8.3]({{< relref "whats-new-in-v8-3" >}}) - [What's new in 8.2]({{< relref "whats-new-in-v8-2" >}}) diff --git a/docs/sources/whatsnew/whats-new-in-v7-0.md b/docs/sources/whatsnew/whats-new-in-v7-0.md index ba2bc196f5c..0aeb5342e61 100644 --- a/docs/sources/whatsnew/whats-new-in-v7-0.md +++ b/docs/sources/whatsnew/whats-new-in-v7-0.md @@ -58,7 +58,7 @@ In the future we will add more workflows and integrations so that correlating be The data you want to visualize can come from many different places and it is usually not in exactly the right form. Users can now transform non-time series data into tables (e.g., JSON files or even simple lookup tables) in seconds without any customization or additional overhead. They can then combine non-time series data with any other data in Grafana; data from an external database or a panel that already exists in one of their current dashboards. -By chaining a simple set of point and click [transformations]({{< relref "../panels/reference-transformation-functions.md" >}}), users will be able join, pivot, filter, re-name and do calculations to get the results they need. Perfect for operations across queries or data sources missing essential data transformations. +By chaining a simple set of point and click [transformations]({{< relref "../panels/transform-data/transformation-functions.md" >}}), users will be able join, pivot, filter, re-name and do calculations to get the results they need. Perfect for operations across queries or data sources missing essential data transformations. [Transformations]({{< relref "../panels/transform-data/about-transformation.md" >}}) also adds the ability to do math across queries. Lots of data sources do not support this natively, so being able to do it in Grafana is a powerful feature. @@ -78,7 +78,7 @@ We are also introducing a new shared data model for both time series and table d - **Outer join:** Joins many time series/tables by a field. This can be used to outer join multiple time series on the _time_ field to show many time series in one table. - **Add field from calculation:** This is a powerful transformation that allows you perform many different types of math operations and add the result as a new field. Can be used to calculate the difference between two series or fields and add the result to a new field. Or multiply one field with another and add the result to a new field. -Learn more about this feature in [Transformations]({{< relref "../panels/reference-transformation-functions.md" >}}). +Learn more about this feature in [Transformations]({{< relref "../panels/transform-data/transformation-functions.md" >}}). ## Field options and overrides diff --git a/docs/sources/whatsnew/whats-new-in-v7-2.md b/docs/sources/whatsnew/whats-new-in-v7-2.md index 2417cbfb298..5d532f913b0 100644 --- a/docs/sources/whatsnew/whats-new-in-v7-2.md +++ b/docs/sources/whatsnew/whats-new-in-v7-2.md @@ -77,8 +77,8 @@ You can now add override rules that use a regex matcher to choose which fields t Grafana 7.2 includes the following transformation enhancements: -- A new [Group By]({{< relref "../panels/reference-transformation-functions.md#group-by">}}) transformation that allows you to group by multiple fields and add any number of aggregations for other fields. -- The [Labels to fields]({{< relref "../panels/reference-transformation-functions.md#labels-to-fields">}}) transformation now allows you to pick one label and use that as the name of the value field. +- A new [Group By]({{< relref "../panels/transform-data/transformation-functions.md#group-by">}}) transformation that allows you to group by multiple fields and add any number of aggregations for other fields. +- The [Labels to fields]({{< relref "../panels/transform-data/transformation-functions.md#labels-to-fields">}}) transformation now allows you to pick one label and use that as the name of the value field. - You can drag transformations to reorder them. Remember that transformations are processed in the order they are listed in the UI, so think before you move something! {{< figure src="/static/img/docs/v72/transformations.gif" max-width="800px" caption="Group by and reordering of transformations" >}} diff --git a/docs/sources/whatsnew/whats-new-in-v7-4.md b/docs/sources/whatsnew/whats-new-in-v7-4.md index a7fcad77c44..aa2908ec258 100644 --- a/docs/sources/whatsnew/whats-new-in-v7-4.md +++ b/docs/sources/whatsnew/whats-new-in-v7-4.md @@ -54,7 +54,7 @@ The following transformations were added in Grafana 7.4. The _Sort by_ transformation allows you to sort data before sending it to the visualization. -For more information, refer to [Sort by]({{< relref "../panels/reference-transformation-functions.md#sort-by" >}}). +For more information, refer to [Sort by]({{< relref "../panels/transform-data/transformation-functions.md#sort-by" >}}). #### Filter data by value transform @@ -62,7 +62,7 @@ The new _Filter data by value_ transformation allows you to filter your data dir This transformation is very useful if your data source does not natively filter by values. You might also use this to narrow values to display if you are using a shared query. -For more information, refer to [Filter data by value]({{< relref "../panels/reference-transformation-functions.md#filter-data-by-value" >}}). +For more information, refer to [Filter data by value]({{< relref "../panels/transform-data/transformation-functions.md#filter-data-by-value" >}}). ### New override option diff --git a/docs/sources/whatsnew/whats-new-in-v8-1.md b/docs/sources/whatsnew/whats-new-in-v8-1.md index 085f6caeabb..9bfcd828c2a 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-1.md +++ b/docs/sources/whatsnew/whats-new-in-v8-1.md @@ -53,7 +53,7 @@ Grafana 8.1 includes some significant enhancements to transformations, including #### Config from query (Beta) -This transformation enables panel config (Threshold, Min, Max, etc.) to be derived from query results. For more information, refer to [Config from query results]({{< relref "../panels/reference-transformation-functions.md#config-from-query-results" >}}). +This transformation enables panel config (Threshold, Min, Max, etc.) to be derived from query results. For more information, refer to [Config from query results]({{< relref "../panels/transform-data/transformation-functions.md#config-from-query-results" >}}). #### Rows to fields (Beta) @@ -75,7 +75,7 @@ Output: As you can see each row in the source data becomes a separate field. Each field now also has a max config option set. Options like **Min**, **Max**, **Unit** and **Thresholds** are all part of field configuration and if set like this will be used by the visualization instead of any options manually configured in the panel editor options pane. -For more on how to use this transformation, refer to [Rows to fields transform]({{< relref "../panels/reference-transformation-functions.md#rows-to-fields" >}}). +For more on how to use this transformation, refer to [Rows to fields transform]({{< relref "../panels/transform-data/transformation-functions.md#rows-to-fields" >}}). #### Contextual & Inline Help diff --git a/docs/sources/whatsnew/whats-new-in-v8-5.md b/docs/sources/whatsnew/whats-new-in-v8-5.md new file mode 100644 index 00000000000..0e783ec44ff --- /dev/null +++ b/docs/sources/whatsnew/whats-new-in-v8-5.md @@ -0,0 +1,130 @@ ++++ +title = "What's new in Grafana v8.5" +description = "Feature and improvement highlights for Grafana v8.5" +keywords = ["grafana", "new", "documentation", "8.5", "release notes"] +weight = -33 +aliases = ["/docs/grafana/latest/guides/whats-new-in-v8-5/"] +[_build] +list = false ++++ + +# What’s new in Grafana v8.5 + +We’re excited to announce Grafana v8.5, with a variety of improvements that focus on Grafana’s usability, performance, and security. + +We’ve summarized what’s new in the release here, but you might also be interested in the announcement blog post as well. If you’d like all the details you can check out the complete [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). + +# OSS + +## Alerting - group names for Grafana-managed alert rules + +It’s been tricky to work with more than a small number of Grafana-managed alert rules without groups in namespaces. They’ve also been inconsistent with the [Prometheus Alert Generator Compliance Specification](https://github.com/prometheus/compliance/blob/main/alert_generator/specification.md), which made working with Grafana-managed and Prometheus-managed alerts a confusing experience. With this release, you can still see flattened Grafana-managed alerts in the “List” tab, or use the new “Grouped” tab to work with groups: + +Choose useful group names, and move alert rules between groups. + +{{< figure src="/static/img/docs/alerting/unified/rule-grouping-8-5.png" max-width="400px" caption="Rule group" >}} + +Rules in a group are evaluated together, so you can also set the interval for the entire group. + +{{< figure src="/static/img/docs/alerting/unified/rule-grouping-details-8-5.png" max-width="350px" caption="Rule group details" >}} + +## Analytics + +You can now enable Grafana version update checking and Grafana plugins version update checking separately using [a new configuration option to separately enable plugin version update checks](https://grafana.com/docs/grafana/latest/administration/configuration/#check_for_plugin_updates). The [prior version update checking configuration](https://grafana.com/docs/grafana/latest/administration/configuration/#check_for_updates) now only controls Grafana version update checks. + +When enabled, a check runs every 10 minutes. It will notify, via the UI, when a new version is available. The checks will not prompt any auto-updates of the Grafana software, nor will it send any sensitive information. + +Plugin update available: + +{{< figure src="/static/img/docs/analytics/github-plugin-update-available-8-5.png" max-width="450px" caption="Grafana-update-available" >}} + +Grafana update available: + +{{< figure src="/static/img/docs/analytics/github-plugin-version-8-5.png" max-width="450px" caption="Grafana-update-available" >}} + +## Dashboard Panels + +In addition to RSS feeds, the News panel now supports Atom feeds, allowing you to display a wider range of data and information in Grafana. + +### Scrolling in the Bar gauge panel + +The Bar gauge panel now supports scrolling to support displaying large datasets while maintaining the readability of labels. You can set a min width or height for the bars (depending on the chart’s orientation), allowing the content to overflow in the container and become scrollable. + +{{< figure src="/static/img/docs/bar-gauge-panel/vertical-view-8-5.png" max-width="400px" caption="Vertical view" >}} + +{{< figure src="/static/img/docs/bar-gauge-panel/horizontal-8-5.png" max-width="400px" caption="Horizontal view" >}} + +## Transformations + +### Template variable substitution + +We’ve added support to substitute template variables to transformations. This allows dashboards to be more interactive with transformations when a user inputs calculations and `$__interval` and `$__interval_ms`. + +### Grouping to matrix + +A new transformation is available that helps you structure data in a matrix format, using the Grafana table plugin. + +## Expanding the navigation bar (Beta) + +Available by switching on the ‘newNavigation’ feature toggle. +You can expand the navigation bar for a better overview of Grafana’s features and installed integrations. +This feature is currently in a beta version and we would appreciate your feedback. Sign up for a call with the Grafana team - it only takes 30 minutes, and you'll receive a $40 USD gift card as a token of appreciation for your time. +US, UK, Canada, Australia, France, Germany, or South Africa: sign up [here](https://www.userinterviews.com/projects/Hz3DyNNwWA/apply); +Everywhere else in the world: sign up [here](https://www.userinterviews.com/projects/Hz3DyNNwWA/apply). + +{{< figure src="/static/img/docs/navigation/new-navigation-8-5.png" max-width="400px" caption="New nav panel" >}} + +## Notifications list for error alerts (Beta) + +Available by switching on the ‘persistNotifications’ feature toggle. +In order to support debugging issues in Grafana, error alerts that appear when viewing a dashboard now include a trace ID, and these alerts can be accessed under Profile / Notifications. + +{{< figure src="/static/img/docs/navigation/nav-profile-notification-8-5.png" max-width="200px" caption="New nav panel" >}} + +## Service accounts (beta) + +Service accounts are a major evolution for machine access within Grafana. You can create multiple API tokens per service account with independent expiration dates, and temporarily disable a service account without deleting it. These benefits make Service Accounts a more flexible way for Terraform and other apps to authenticate with Grafana. Service accounts also work with [fine-grained access control](https://grafana.com/docs/grafana/latest/enterprise/access-control/) in [Grafana Enterprise](https://grafana.com/docs/grafana/latest/enterprise/): you can improve security by granting service accounts specific roles to limit the functions they can perform. Service accounts are available in beta; you can try them out by enabling the `service-accounts` [feature toggle](https://grafana.com/docs/grafana/latest/administration/service-accounts/enable-service-accounts) or, if you use Grafana Cloud, [reaching out to our support team](https://grafana.com/orgs/raintank/tickets#) for early access. Learn more about Service Accounts in our [docs](https://grafana.com/docs/grafana/latest/administration/service-accounts). + +{{< figure src="/static/img/docs/service-accounts/configure-8-5.png" max-width="400px" caption="Configure service accounts" >}} + +## Observability + +### Trace to Logs for Splunk + +With Trace to Logs, you can view relevant logs for a trace or span with one click. You can now link to Splunk logs from your tracing datasource. In your tracing datasource, configure Trace to Logs by selecting the Splunk datasource and relevant query options like tags to include in the query. + +## Experimental Explore to Dashboard workflow + +Allows users to create panels directly from within explore. + +All queries in Explore get copied over to the new panel, the panel type is automatically selected based on queries’ response +With multiple queries, it will view the response from the first, non hidden query to determine the visualization type + +This feature is behind the `explore2Dashboard` feature toggle and is enabled by default. + +# Grafana Enterprise + +## Security + +### Fine-Grained Access Control comes to Alerting (beta) + +Check the Grafana Enterprise / Security section below for more details, including how to enable this beta feature; we’ve implemented +[fine-grained access control](https://grafana.com/docs/grafana/latest/enterprise/access-control/) for alerting rules, notification policies, and contact points in [Grafana Enterprise](https://grafana.com/docs/grafana/latest/enterprise/). You can turn on fine-grained access control using the `accesscontrol` [feature toggle](https://grafana.com/docs/grafana/latest/enterprise/access-control/#enable-fine-grained-access-control), or by [reaching out to our support team](https://grafana.com/orgs/raintank/tickets#) for early access if you use Grafana Cloud. For more information on fine-grained access control, visit our [docs](https://grafana.com/docs/grafana/latest/enterprise/access-control/). + +{{< figure src="/static/img/docs/enterprise/fine-grained-access-control-8-5.png" max-width="400px" caption="Fine grained access control" >}} + +### Control access to dashboards, folders, and annotations (beta) + +You can now use fine-grained access control to manage which specific users, teams, and roles can create, read, update, or delete dashboards, folders, or annotations. These are the latest services to incorporate fine-grained access control, which helps you dial in the specific access your users should have in Grafana. Fine-grained access control is currently in beta, but general availability is just around the corner, planned for our 9.0 release. You can turn on fine-grained access control using the `accesscontrol` [feature toggle](https://grafana.com/docs/grafana/latest/enterprise/access-control/#enable-fine-grained-access-control), or by [reaching out to our support team](https://grafana.com/orgs/raintank/tickets#) for early access if you use Grafana Cloud. For more information on fine-grained access control, visit our [docs](https://grafana.com/docs/grafana/latest/enterprise/access-control/). + +{{< figure src="/static/img/docs/enterprise/configure-role-access-8-5.png" max-width="400px" caption="Configure role access" >}} + +### Configure Azure Key Vault using Managed Identities + +You can already keep secrets in Grafana’s database (like data source credentials) safer by retrieving your database encryption key from a Key Management Service, like AWS KMS or Azure Key Vault. In Grafana v8.5, you can use an Azure Managed Identity to integrate with Azure Key Vault. This simplifies the Key Vault integration and keeps it consistent with Grafana data sources, like Azure Data Explorer. + +## Configure reports more easily + +Reports are a great way to share Grafana dashboards by email with users who don’t regularly sign in to Grafana. In 8.5, we’ve revamped the Report authoring UI to make it quicker and easier for you to create reports. View report details at a glance in list view, consider one configuration step at a time, and save reports for later. Also, Grafana will now emit a log every time a report is sent, so you can confirm its status or learn about send errors. Learn more about Reporting in our [docs](https://grafana.com/docs/grafana/latest/enterprise/reporting/). + +{{< figure src="/static/img/docs/enterprise/report-new-report-8-5.png" max-width="350px" caption="New report" >}} diff --git a/e2e/dashboards-suite/dashboard-time-zone.spec.ts b/e2e/dashboards-suite/dashboard-time-zone.spec.ts index e2e8a69432e..da3924ac58d 100644 --- a/e2e/dashboards-suite/dashboard-time-zone.spec.ts +++ b/e2e/dashboards-suite/dashboard-time-zone.spec.ts @@ -1,4 +1,3 @@ -import { e2e } from '@grafana/e2e'; import { addDays, addHours, @@ -10,6 +9,8 @@ import { toDate, } from 'date-fns'; +import { e2e } from '@grafana/e2e'; + e2e.scenario({ describeName: 'Dashboard time zone support', itName: 'Tests dashboard time zone scenarios', diff --git a/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts b/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts index 2c4fe236baf..c2eb79f780c 100644 --- a/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts +++ b/e2e/dashboards-suite/templating-dashboard-links-and-variables.spec.ts @@ -34,7 +34,7 @@ e2e.scenario({ expect(links).to.have.length.greaterThan(13); for (let index = 0; index < links.length; index++) { - expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${encodeURI(variableValue)}`); + expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`); } }); }; @@ -44,14 +44,6 @@ e2e.scenario({ // verify all links, should have All value verifyLinks('All'); - // Data links should percent encode var values - e2e() - .get('[aria-label="Data link"]') - .should('exist') - .and((link) => { - expect(link.attr('href')).contains(encodeURI('test%25value')); - }); - e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('All').should('be.visible').click(); e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('p2').should('be.visible').click(); diff --git a/e2e/panels-suite/panelEdit_queries.spec.ts b/e2e/panels-suite/panelEdit_queries.spec.ts index 0276011ae2f..c6c46223064 100644 --- a/e2e/panels-suite/panelEdit_queries.spec.ts +++ b/e2e/panels-suite/panelEdit_queries.spec.ts @@ -1,4 +1,5 @@ import { e2e } from '@grafana/e2e'; + import { expect } from '../../public/test/lib/common'; const flakyTimeout = 10000; diff --git a/e2e/smoke-tests-suite/1-smoketests.spec.ts b/e2e/smoke-tests-suite/1-smoketests.spec.ts index 2049b329695..6f2b7a19311 100644 --- a/e2e/smoke-tests-suite/1-smoketests.spec.ts +++ b/e2e/smoke-tests-suite/1-smoketests.spec.ts @@ -1,4 +1,5 @@ import { e2e } from '@grafana/e2e'; + import { smokeTestScenario } from '../shared/smokeTestScenario'; e2e.scenario(smokeTestScenario); diff --git a/e2e/verify/specs/smoketests.spec.ts b/e2e/verify/specs/smoketests.spec.ts index 185eb389e0f..d3c99b62166 100644 --- a/e2e/verify/specs/smoketests.spec.ts +++ b/e2e/verify/specs/smoketests.spec.ts @@ -1,4 +1,5 @@ import { e2e } from '@grafana/e2e'; + import { smokeTestScenario } from '../../shared/smokeTestScenario'; e2e.scenario(smokeTestScenario); diff --git a/go.mod b/go.mod index c5e28247313..55c4cabade7 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 github.com/fatih/color v1.13.0 github.com/gchaincl/sqlhooks v1.3.0 - github.com/getsentry/sentry-go v0.10.0 + github.com/getsentry/sentry-go v0.13.0 github.com/go-kit/kit v0.11.0 // indirect github.com/go-openapi/strfmt v0.20.2 github.com/go-redis/redis/v8 v8.11.4 @@ -51,7 +51,7 @@ require ( github.com/gorilla/websocket v1.4.2 github.com/gosimple/slug v1.9.0 github.com/grafana/cuetsy v0.0.0-20211119211437-8c25464cc9bf - github.com/grafana/grafana-aws-sdk v0.10.1 + github.com/grafana/grafana-aws-sdk v0.10.3 github.com/grafana/grafana-azure-sdk-go v1.1.0 github.com/grafana/grafana-plugin-sdk-go v0.131.0 github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa @@ -100,11 +100,11 @@ require ( go.opentelemetry.io/collector/model v0.31.0 go.opentelemetry.io/otel v1.6.3 go.opentelemetry.io/otel/exporters/jaeger v1.0.0 - go.opentelemetry.io/otel/sdk v1.0.0 + go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/trace v1.6.3 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 golang.org/x/exp v0.0.0-20210220032938-85be41e4509f - golang.org/x/net v0.0.0-20220401154927-543a649e0bdd // indirect + golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 @@ -144,7 +144,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee // indirect - github.com/cenkalti/backoff/v4 v4.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/centrifugal/protocol v0.7.6 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect @@ -183,9 +183,9 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-multierror v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // indirect @@ -216,7 +216,7 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc // indirect github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect - github.com/rs/cors v1.8.0 // indirect + github.com/rs/cors v1.8.2 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/encoding v0.3.2 github.com/sercand/kuberesolver v2.4.0+incompatible // indirect @@ -232,12 +232,12 @@ require ( go.mongodb.org/mongo-driver v1.7.0 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 - go.uber.org/goleak v1.1.11 // indirect - golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect + go.uber.org/goleak v1.1.12 // indirect + golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de + google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect ) @@ -246,6 +246,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0 github.com/Azure/go-autorest/autorest/adal v0.9.17 + github.com/armon/go-radix v1.0.0 github.com/golang-migrate/migrate/v4 v4.7.0 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508 @@ -269,20 +270,26 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect - github.com/containerd/containerd v1.5.9 // indirect + github.com/containerd/containerd v1.6.2 // indirect github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect github.com/getkin/kin-openapi v0.94.0 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/klauspost/compress v1.15.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect github.com/segmentio/asm v1.1.1 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect + k8s.io/api v0.22.5 // indirect + k8s.io/apimachinery v0.22.5 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect ) // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream @@ -292,7 +299,17 @@ replace github.com/apache/thrift => github.com/apache/thrift v0.14.1 replace github.com/hashicorp/consul => github.com/hashicorp/consul v1.10.2 +replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.7 + +replace github.com/tidwall/gjson => github.com/tidwall/gjson v1.14.1 + +// Upgraded to fix CVE-2020-26066. This can be removed when go.opentelemetry.io/collector and github.com/influxdata/telegraf are upgraded +// github.com/tidwall/match v1.0.1 should not be used. +replace github.com/tidwall/match => github.com/tidwall/match v1.1.1 + // Thema's thema CLI requires cobra, which eventually works its way down to go-hclog@v1.0.0. // Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593 // No harm to Thema because it's only a dependency in its main package. replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1 + +replace github.com/microcosm-cc/bluemonday => github.com/microcosm-cc/bluemonday v1.0.18 diff --git a/go.sum b/go.sum index 291c9de9a53..b6a9645888a 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= @@ -94,6 +95,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-amqp-common-go/v3 v3.0.0/go.mod h1:SY08giD/XbhTz07tJdpw1SoxQXHPN30+DI3Z04SYqyg= github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= @@ -149,6 +151,8 @@ github.com/Azure/go-amqp v0.12.6/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -257,8 +261,10 @@ github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -267,7 +273,10 @@ github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2 github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -316,6 +325,7 @@ github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8V github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922 h1:8ypNbf5sd3Sm3cKJ9waOGoQv6dKAFiFty9L6NP1AqJ4= github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/alicebob/miniredis/v2 v2.14.3/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I= @@ -347,6 +357,7 @@ github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4 github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= @@ -431,6 +442,7 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4Nx github.com/aws/smithy-go v1.5.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -488,8 +500,9 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrifugal/centrifuge v0.19.0 h1:YHws0dRpgsBiI73tRl1wwaB13gzuaI1AM4IFcQQQqcw= @@ -497,6 +510,7 @@ github.com/centrifugal/centrifuge v0.19.0/go.mod h1:O2elf8q3Qkie3z97wkqVqxB52pnO github.com/centrifugal/protocol v0.7.6 h1:AfMwTZfwnFwZslIzQL4QtRnWSVO32RPSuk4iNS/V9tg= github.com/centrifugal/protocol v0.7.6/go.mod h1:cJo0/BuXglhPfg0fgSgTXvBZ7y+9rdg4+nPbIDOVmlA= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -506,6 +520,7 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= @@ -522,6 +537,7 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/cisco-ie/nx-telemetry-proto v0.0.0-20190531143454-82441e232cf6/go.mod h1:ugEfq4B8T8ciw/h5mCkgdiDRFS4CkqqhH2dymDB4knc= @@ -566,11 +582,13 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -588,8 +606,10 @@ github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09Zvgq github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= -github.com/containerd/containerd v1.5.9 h1:rs6Xg1gtIxaeyG+Smsb/0xaSDu1VgFhOCKBXxMxbsF4= -github.com/containerd/containerd v1.5.9/go.mod h1:fvQqCfadDGga5HZyn3j4+dx56qj2I9YwBrlSdalvJYQ= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= +github.com/containerd/containerd v1.6.2 h1:pcaPUGbYW8kBw6OgIZwIVIeEhdWVrBzsoCfVJ5BjrLU= +github.com/containerd/containerd v1.6.2/go.mod h1:sidY30/InSE1j2vdD1ihtKoJz+lWdaXMdiAeIupaf+s= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -597,6 +617,7 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -605,6 +626,8 @@ github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1S github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -614,9 +637,11 @@ github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= @@ -635,11 +660,14 @@ github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNR github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -648,6 +676,7 @@ github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -692,6 +721,7 @@ github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= @@ -758,6 +788,7 @@ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwu github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -766,6 +797,7 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -774,6 +806,7 @@ github.com/docker/docker v20.10.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05b github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.8+incompatible h1:RVqD337BgQicVCzYrrlhLDWhq6OAD2PJDUg2LsEUvKM= github.com/docker/docker v20.10.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -849,7 +882,6 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -892,18 +924,14 @@ github.com/getkin/kin-openapi v0.91.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSy github.com/getkin/kin-openapi v0.94.0 h1:bAxg2vxgnHHHoeefVdmGbR+oxtJlcv5HsJJa3qmAHuo= github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g= -github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws= +github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo= +github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/glinton/ping v0.1.4-0.20200311211934-5ac87da8cd96/go.mod h1:uY+1eqFUyotrQxF1wYFNtMeHp/swbYRsoGzfcPZ8x3o= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -943,9 +971,12 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -1079,11 +1110,8 @@ github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= @@ -1150,6 +1178,7 @@ github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v2.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -1174,8 +1203,9 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= github.com/gogo/status v1.1.0 h1:+eIkrewn5q6b30y+g/BJINVVdi2xH7je5MPJ3ZPK3JA= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= @@ -1263,6 +1293,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -1360,6 +1391,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de h1:F7WD09S8QB4LrkEpka0dFPLSotH11HRpCsLIbIcJ7sU= github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -1383,8 +1415,8 @@ github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f h1:FvvSVEbnGeM2bUivG github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f/go.mod h1:uPG2nyK4CtgNDmWv7qyzYcdI+S90kHHRWvHnBtEMBXM= github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA= github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/grafana/grafana-aws-sdk v0.10.1 h1:Ksguhjx6EuGLN/5Oc7oZoxuDReJ5RxIH99yqSMpLGUs= -github.com/grafana/grafana-aws-sdk v0.10.1/go.mod h1:vFIOHEnY1u5nY0/tge1IHQjPuG6DRKr2ISf/HikUdjE= +github.com/grafana/grafana-aws-sdk v0.10.3 h1:nqjK8NfrUyUcAtSjGxnt17ZKjeOMCi62MNKFNagjG6g= +github.com/grafana/grafana-aws-sdk v0.10.3/go.mod h1:vFIOHEnY1u5nY0/tge1IHQjPuG6DRKr2ISf/HikUdjE= github.com/grafana/grafana-azure-sdk-go v1.1.0 h1:Gh0fjs7jr4Lp5y+4cjn48U+MQaJeV8i9m1ds/1xszto= github.com/grafana/grafana-azure-sdk-go v1.1.0/go.mod h1:rgrnK9m6CgKlgx4rH3FFP/6dTdyRO6LYC2mVZov35yo= github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2ud7NNM7LrGPO4x0NFR8qLq68CqI4SmB7I2yRN2w9oE= @@ -1451,8 +1483,9 @@ github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPA github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -1476,8 +1509,9 @@ github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9 github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= github.com/hashicorp/go-plugin v1.2.2/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= @@ -1620,12 +1654,14 @@ github.com/influxdata/telegraf v1.16.3/go.mod h1:fX/6k7qpIqzVPWyeIamb0wN5hbwc0AN github.com/influxdata/toml v0.0.0-20190415235208-270119a8ce65/go.mod h1:zApaNFpP/bTpQItGZNNUMISDMDAnTXu9UqJ4yT3ocz8= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/influxdata/wlog v0.0.0-20160411224016-7c63b0a71ef8/go.mod h1:/2NMgWB1DHM1ti/gqhOlg+LJeBVk6FqR5aVGYY0hlwI= +github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -1690,6 +1726,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -1731,7 +1768,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= @@ -1793,8 +1829,8 @@ github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LE github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/laher/mergefs v0.1.1 h1:nV2bTS57vrmbMxeR6uvJpI8LyGl3QHj4bLBZO3aUV58= github.com/laher/mergefs v0.1.1/go.mod h1:FSY1hYy94on4Tz60waRMGdO1awwS23BacqJlqf9lJ9Q= @@ -1803,7 +1839,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/ragel-machinery v0.0.0-20181214104525-299bdde78165/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg= github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= @@ -1824,6 +1859,7 @@ github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZi github.com/linode/linodego v0.28.5/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM= github.com/linode/linodego v0.32.0 h1:IK04cx2b/IwAAd6XLruf1Dl/n3dRXj87Uw/5qo6afVU= github.com/linode/linodego v0.32.0/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lovoo/gcloud-opentracing v0.3.0/go.mod h1:ZFqk2y38kMDDikZPAK7ynTTGuyt17nSPdS3K5e+ZTBY= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= @@ -1868,6 +1904,7 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= @@ -1894,6 +1931,8 @@ github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= @@ -1905,6 +1944,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mdlayher/apcupsd v0.0.0-20200608131503-2bf01da7bf1b/go.mod h1:WYK/Z/aXq9cbMFIL5ihcA4sX/r/3/WCas/Qvs/2fXcA= github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= @@ -1914,7 +1954,7 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK github.com/mdlayher/wifi v0.0.0-20190303161829-b1436901ddee/go.mod h1:Evt/EIne46u9PtQbeTx2NTcqURpr5K4SvKtGmBuDPN8= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -1977,9 +2017,13 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -2061,6 +2105,7 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -2076,6 +2121,7 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -2088,6 +2134,7 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -2096,6 +2143,7 @@ github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -2106,6 +2154,7 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= github.com/opentracing-contrib/go-grpc v0.0.0-20191001143057-db30781987df/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= @@ -2335,8 +2384,9 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -2356,6 +2406,7 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -2370,11 +2421,14 @@ github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrY github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44 h1:3egqo0Vut6daANFm7tOXdNAa8v5/uLU+sgCJrc88Meo= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06qF79/FKAJpBvFx3P8Ww4UTIMAe+lpNXDHziac= github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= github.com/segmentio/asm v1.1.0/go.mod h1:4EUJGaKsB8ImLUwOGORVsNd9vTRDeh44JGsY4aKp5I4= github.com/segmentio/asm v1.1.1 h1:WzUpP9BRnRgoP+v8qywthpSe9U5KLq1uDGdJBEcSeQo= @@ -2451,6 +2505,7 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -2515,7 +2570,7 @@ github.com/thanos-io/thanos v0.13.1-0.20210401085038-d7dff0c84d17/go.mod h1:zU8K github.com/thanos-io/thanos v0.22.0/go.mod h1:SZDWz3phcUcBr4MYFoPFRvl+Z9Nbi45HlwQlwSZSt+Q= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= @@ -2530,9 +2585,11 @@ github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZF github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tonistiigi/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:Q5IRRDY+cjIaiOjTAnXN5LKQV5MPqVx5ofQn85Jy5Yw= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= github.com/uber-go/tally v3.3.15+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= @@ -2580,9 +2637,11 @@ github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5 github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vjeantet/grok v1.0.0/go.mod h1:/FWYEVYekkm+2VjcFmO9PufDU5FgXHUz9oy2EGqmQBo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/vmware/govmomi v0.19.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= @@ -2678,8 +2737,11 @@ go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVM go.etcd.io/etcd/client/v3 v3.5.0-alpha.0.0.20210225194612-fa82d11a958a/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0/go.mod h1:tV31atvwzcybuqejDoY3oaNRTtlD2l/Ot78Pc9w7DMY= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0-alpha.0/go.mod h1:FAwse6Zlm5v4tEWZaTjmNhe17Int4Oxbu7+2r0DiD3w= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0-alpha.0.0.20210225194612-fa82d11a958a/go.mod h1:tsKetYpt980ZTpzl/gb+UOJj9RkIyCb1u4wjzMg90BQ= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.0.4/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -2711,28 +2773,48 @@ go.opentelemetry.io/collector v0.31.0 h1:Lxrsl/fhYQTcsrAOwklQpKi8T+ghxLEmmUjFqey go.opentelemetry.io/collector v0.31.0/go.mod h1:A9vKmEa2MI/vJXNUoRinq9w25ZMmxWJLYXYELHkBEw0= go.opentelemetry.io/collector/model v0.31.0 h1:IgMOkSBd/n/gV4EQQ1nJ+/ylddOlqTfMGjku91yC0d8= go.opentelemetry.io/collector/model v0.31.0/go.mod h1:PcHNnM+RUl0uD8VkSn93PO78N7kQYhfqpI/eki57pl4= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib v0.21.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.21.0/go.mod h1:Vm5u/mtkj1OMhtao0v+BGo2LUoLCgHYXvRmj0jWITlE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0/go.mod h1:JQAtechjxLEL81EjmbRwxBq/XEzGaHcsPuDHAx54hg4= go.opentelemetry.io/contrib/zpages v0.0.0-20210722161726-7668016acb73/go.mod h1:NAkejuYm41lpyL43Fu1XdnCOYxN5NVV80/MJ03JQ/X8= go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel/exporters/jaeger v1.0.0 h1:cLhx8llHw02h5JTqGqaRbYn+QVKHmrzD9vEbKnSPk5U= go.opentelemetry.io/otel/exporters/jaeger v1.0.0/go.mod h1:q10N1AolE1JjqKrFJK2tYw0iZpmX+HBaXBtuCzRnBGQ= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= -go.opentelemetry.io/otel/sdk v1.0.0 h1:BNPMYUONPNbLneMttKSjQhOTlFLOD9U22HNG1KrIN2Y= go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= +go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.starlark.net v0.0.0-20200901195727-6e684ef5eeee/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -2747,8 +2829,9 @@ go.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADb go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -2971,16 +3054,21 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220401154927-543a649e0bdd h1:zYlwaUHTmxuf6H7hwO2dgwqozQmH7zf4x+/qql4oVWc= golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8= +golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -3170,14 +3258,18 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3191,11 +3283,13 @@ golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY= golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3258,6 +3352,7 @@ golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -3300,6 +3395,7 @@ golang.org/x/tools v0.0.0-20200317043434-63da46f3035e/go.mod h1:Sl4aGygMT6LrqrWc golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200422205258-72e4a01eba43/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -3307,6 +3403,7 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200603131246-cc40288be839/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200612220849-54c614fe050c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -3317,6 +3414,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200822203824-307de81be3f4/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201014170642-d1624618ad65/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201020161133-226fd2f889ca/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -3461,6 +3559,7 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200710124503-20a17af7bd0e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -3527,8 +3626,9 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k= google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3 h1:SeX3QUcBj3fciwnfPT9kt5gBhFy/FCZtYZ+I/RB8agc= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -3571,6 +3671,7 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= @@ -3615,9 +3716,6 @@ gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBl gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -3704,8 +3802,9 @@ k8s.io/api v0.20.5/go.mod h1:FQjAceXnVaWDeov2YUWhOb6Yt+5UjErkp6UO3nczO1Y= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s= -k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= +k8s.io/api v0.22.5 h1:xk7C+rMjF/EGELiD560jdmwzrB788mfcHiNbMQLIVI8= +k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8= k8s.io/apimachinery v0.0.0-20191115015347-3c7067801da2/go.mod h1:dXFS2zaQR8fyzuvRdJDHw2Aerij/yVGJSre0bZQSVJA= k8s.io/apimachinery v0.16.9/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE= @@ -3724,22 +3823,29 @@ k8s.io/apimachinery v0.20.5/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= -k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.22.5 h1:cIPwldOYm1Slq9VLBRPtEYpyhjIm1C6aAMAoENuvN9s= +k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -3753,8 +3859,9 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.10.0 h1:R2HDMDJsHVTHA2n4RjwbeYXdOcBymXdX/JRb1v0VGhE= k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190722073852-5e22f3d471e6/go.mod h1:RZvgC8MSN6DjiMV6oIfEE9pDL9CYXokkfaCKZeHm3nc= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= @@ -3765,13 +3872,16 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 h1:imL9YgXQ9p7xmPzHFm/vVd/cF78jad+n4wK1ABwYtMM= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= modernc.org/httpfs v1.0.0/go.mod h1:BSkfoMUcahSijQD5J/Vu4UMOxzmEf5SNRwyXC4PJBEw= modernc.org/libc v1.3.1/go.mod h1:f8sp9GAfEyGYh3lsRIKtBh/XwACdFvGznxm6GJmQvXk= @@ -3786,6 +3896,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= diff --git a/latest.json b/latest.json index 9a36ea00bc2..29632292cfa 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "8.4.6", - "testing": "8.5.0-beta1" + "stable": "8.5.0", + "testing": "8.5.0" } diff --git a/package.json b/package.json index 2706e103b50..f29a1f170c9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint:ts": "eslint . --ext .js,.tsx,.ts --cache", "lint:sass": "yarn stylelint '{public/sass,packages}/**/*.scss' --cache", "test:ci": "betterer ci && mkdir -p reports/junit && JEST_JUNIT_OUTPUT_DIR=reports/junit jest --ci --reporters=default --reporters=jest-junit -w ${TEST_MAX_WORKERS:-100%}", - "lint:fix": "yarn lint --fix", + "lint:fix": "yarn lint:ts --fix", "packages:build": "lerna run clean && lerna run build --ignore @grafana-plugins/input-datasource", "packages:docsExtract": "rm -rf ./reports/docs && lerna run docsExtract", "packages:docsToMarkdown": "api-documenter markdown --input-folder ./reports/docs/ --output-folder ./docs/sources/packages_api/ --hugo", @@ -113,7 +113,7 @@ "@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.4", "@testing-library/react-hooks": "7.0.2", - "@testing-library/user-event": "13.5.0", + "@testing-library/user-event": "14.1.1", "@types/angular": "1.8.4", "@types/angular-route": "1.7.2", "@types/classnames": "2.3.0", @@ -182,6 +182,7 @@ "enzyme-to-json": "3.6.2", "eslint": "8.11.0", "eslint-config-prettier": "8.5.0", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-jest": "26.1.2", "eslint-plugin-jsdoc": "38.0.6", "eslint-plugin-lodash": "7.4.0", @@ -249,7 +250,7 @@ "@grafana/aws-sdk": "0.0.35", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "0.0.2-canary.22", + "@grafana/experimental": "^0.0.2-canary.25", "@grafana/google-sdk": "0.0.3", "@grafana/lezer-logql": "^0.0.11", "@grafana/runtime": "workspace:*", @@ -257,7 +258,7 @@ "@grafana/slate-react": "0.22.10-grafana", "@grafana/ui": "workspace:*", "@jaegertracing/jaeger-ui-components": "workspace:*", - "@kusto/monaco-kusto": "5.0.0", + "@kusto/monaco-kusto": "5.1.1", "@lezer/common": "0.15.12", "@lezer/lr": "0.15.8", "@lingui/core": "3.13.2", @@ -319,6 +320,7 @@ "js-yaml": "^4.1.0", "json-source-map": "0.6.1", "jsurl": "^0.1.5", + "kbar": "^0.1.0-beta.30", "lezer-promql": "0.22.0", "lodash": "4.17.21", "logfmt": "^1.3.2", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index b4252fa7bb3..943ec2e1a97 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -51,7 +51,7 @@ "@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.4", "@testing-library/react-hooks": "7.0.2", - "@testing-library/user-event": "13.5.0", + "@testing-library/user-event": "14.1.1", "@types/history": "4.7.11", "@types/jest": "27.4.1", "@types/jquery": "3.5.14", diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts index 980cb3e04e7..f763dcad48a 100644 --- a/packages/grafana-data/rollup.config.ts +++ b/packages/grafana-data/rollup.config.ts @@ -1,9 +1,9 @@ -import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; -import sourceMaps from 'rollup-plugin-sourcemaps'; import json from '@rollup/plugin-json'; -import { terser } from 'rollup-plugin-terser'; +import resolve from '@rollup/plugin-node-resolve'; import path from 'path'; +import sourceMaps from 'rollup-plugin-sourcemaps'; +import { terser } from 'rollup-plugin-terser'; const pkg = require('./package.json'); diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts index bdc06797d82..20001cb4913 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.test.ts @@ -1,6 +1,7 @@ +import { FieldType, DataFrame } from '../types'; + import { ArrayDataFrame } from './ArrayDataFrame'; import { toDataFrameDTO } from './processDataFrame'; -import { FieldType, DataFrame } from '../types'; describe('Array DataFrame', () => { const input = [ diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts index ff69a9efbb0..83b23d23778 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts @@ -1,8 +1,9 @@ -import { Field, FieldType, DataFrame } from '../types/dataFrame'; -import { vectorToArray } from '../vector/vectorToArray'; import { Vector, QueryResultMeta } from '../types'; -import { guessFieldTypeFromNameAndValue, toDataFrameDTO } from './processDataFrame'; +import { Field, FieldType, DataFrame } from '../types/dataFrame'; import { FunctionalVector } from '../vector/FunctionalVector'; +import { vectorToArray } from '../vector/vectorToArray'; + +import { guessFieldTypeFromNameAndValue, toDataFrameDTO } from './processDataFrame'; /** @public */ export type ValueConverter = (val: any) => T; diff --git a/packages/grafana-data/src/dataframe/CircularDataFrame.ts b/packages/grafana-data/src/dataframe/CircularDataFrame.ts index 890be6c2f50..ea39654d47c 100644 --- a/packages/grafana-data/src/dataframe/CircularDataFrame.ts +++ b/packages/grafana-data/src/dataframe/CircularDataFrame.ts @@ -1,6 +1,7 @@ -import { MutableDataFrame } from './MutableDataFrame'; import { CircularVector } from '../vector/CircularVector'; +import { MutableDataFrame } from './MutableDataFrame'; + interface CircularOptions { append?: 'head' | 'tail'; capacity?: number; diff --git a/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts b/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts index b9c73ae4de9..b9fadc6c4aa 100644 --- a/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts +++ b/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts @@ -1,4 +1,5 @@ import { FieldType } from '../types/dataFrame'; + import { DataFrameJSON, dataFrameFromJSON } from './DataFrameJSON'; describe('DataFrame JSON', () => { diff --git a/packages/grafana-data/src/dataframe/DataFrameJSON.ts b/packages/grafana-data/src/dataframe/DataFrameJSON.ts index d99e87ad22d..e57984fb3ba 100644 --- a/packages/grafana-data/src/dataframe/DataFrameJSON.ts +++ b/packages/grafana-data/src/dataframe/DataFrameJSON.ts @@ -1,5 +1,6 @@ import { DataFrame, FieldType, FieldConfig, Labels, QueryResultMeta } from '../types'; import { ArrayVector } from '../vector'; + import { guessFieldTypeFromNameAndValue } from './processDataFrame'; /** diff --git a/packages/grafana-data/src/dataframe/DataFrameView.test.ts b/packages/grafana-data/src/dataframe/DataFrameView.test.ts index 91368341aba..8975da7b24f 100644 --- a/packages/grafana-data/src/dataframe/DataFrameView.test.ts +++ b/packages/grafana-data/src/dataframe/DataFrameView.test.ts @@ -1,7 +1,8 @@ -import { FieldType, DataFrameDTO } from '../types/dataFrame'; import { DateTime } from '../datetime/moment_wrapper'; -import { MutableDataFrame } from './MutableDataFrame'; +import { FieldType, DataFrameDTO } from '../types/dataFrame'; + import { DataFrameView } from './DataFrameView'; +import { MutableDataFrame } from './MutableDataFrame'; interface MySpecialObject { time: DateTime; diff --git a/packages/grafana-data/src/dataframe/DataFrameView.ts b/packages/grafana-data/src/dataframe/DataFrameView.ts index 9f8880d1aa5..9f75cc701e7 100644 --- a/packages/grafana-data/src/dataframe/DataFrameView.ts +++ b/packages/grafana-data/src/dataframe/DataFrameView.ts @@ -1,5 +1,5 @@ -import { DataFrame, Field } from '../types/dataFrame'; import { DisplayProcessor } from '../types'; +import { DataFrame, Field } from '../types/dataFrame'; import { FunctionalVector } from '../vector/FunctionalVector'; /** diff --git a/packages/grafana-data/src/dataframe/FieldCache.test.ts b/packages/grafana-data/src/dataframe/FieldCache.test.ts index 80423614fb2..f6d5a90c8ca 100644 --- a/packages/grafana-data/src/dataframe/FieldCache.test.ts +++ b/packages/grafana-data/src/dataframe/FieldCache.test.ts @@ -1,5 +1,6 @@ -import { FieldCache } from './FieldCache'; import { FieldType } from '../types/dataFrame'; + +import { FieldCache } from './FieldCache'; import { toDataFrame } from './processDataFrame'; describe('FieldCache', () => { diff --git a/packages/grafana-data/src/dataframe/MutableDataFrame.test.ts b/packages/grafana-data/src/dataframe/MutableDataFrame.test.ts index 36881613bdc..7718c62e469 100644 --- a/packages/grafana-data/src/dataframe/MutableDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/MutableDataFrame.test.ts @@ -1,4 +1,5 @@ import { DataFrameDTO, FieldType } from '../types/dataFrame'; + import { MutableDataFrame } from './MutableDataFrame'; describe('Reversing DataFrame', () => { diff --git a/packages/grafana-data/src/dataframe/MutableDataFrame.ts b/packages/grafana-data/src/dataframe/MutableDataFrame.ts index 2597aa17d8f..3b4a603268b 100644 --- a/packages/grafana-data/src/dataframe/MutableDataFrame.ts +++ b/packages/grafana-data/src/dataframe/MutableDataFrame.ts @@ -1,12 +1,14 @@ -import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame'; -import { QueryResultMeta } from '../types/data'; -import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame'; import { isString } from 'lodash'; -import { makeFieldParser } from '../utils/fieldParser'; + +import { QueryResultMeta } from '../types/data'; +import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame'; import { MutableVector, Vector } from '../types/vector'; +import { makeFieldParser } from '../utils/fieldParser'; import { ArrayVector } from '../vector/ArrayVector'; import { FunctionalVector } from '../vector/FunctionalVector'; +import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame'; + export type MutableField = Field>; type MutableVectorCreator = (buffer?: any[]) => MutableVector; diff --git a/packages/grafana-data/src/dataframe/dimensions.ts b/packages/grafana-data/src/dataframe/dimensions.ts index e3b145566d0..629ddcbae7c 100644 --- a/packages/grafana-data/src/dataframe/dimensions.ts +++ b/packages/grafana-data/src/dataframe/dimensions.ts @@ -1,5 +1,5 @@ -import { Field } from '../types/dataFrame'; import { KeyValue } from '../types/data'; +import { Field } from '../types/dataFrame'; export interface Dimension { // Name of the dimension diff --git a/packages/grafana-data/src/dataframe/frameComparisons.test.ts b/packages/grafana-data/src/dataframe/frameComparisons.test.ts index efd3e242e8f..a5feae7d51c 100644 --- a/packages/grafana-data/src/dataframe/frameComparisons.test.ts +++ b/packages/grafana-data/src/dataframe/frameComparisons.test.ts @@ -1,4 +1,5 @@ import { FieldType } from '../types/dataFrame'; + import { compareDataFrameStructures, compareArrayValues } from './frameComparisons'; import { toDataFrame } from './processDataFrame'; diff --git a/packages/grafana-data/src/dataframe/processDataFrame.test.ts b/packages/grafana-data/src/dataframe/processDataFrame.test.ts index 29bc386967e..0683697263e 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.test.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.test.ts @@ -1,3 +1,8 @@ +import { dateTime } from '../datetime/moment_wrapper'; +import { DataFrameDTO, FieldType, TableData, TimeSeries } from '../types/index'; + +import { ArrayDataFrame } from './ArrayDataFrame'; +import { MutableDataFrame } from './MutableDataFrame'; import { guessFieldTypeFromValue, guessFieldTypes, @@ -7,10 +12,7 @@ import { toDataFrame, toLegacyResponseData, } from './processDataFrame'; -import { DataFrameDTO, FieldType, TableData, TimeSeries } from '../types/index'; -import { dateTime } from '../datetime/moment_wrapper'; -import { MutableDataFrame } from './MutableDataFrame'; -import { ArrayDataFrame } from './ArrayDataFrame'; + import { getFieldTypeFromValue } from '.'; describe('toDataFrame', () => { diff --git a/packages/grafana-data/src/dataframe/processDataFrame.ts b/packages/grafana-data/src/dataframe/processDataFrame.ts index d3478cac95b..c1148f5ebff 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.ts @@ -2,6 +2,9 @@ import { isArray, isBoolean, isNumber, isString } from 'lodash'; // Types +import { isDateTime } from '../datetime/moment_wrapper'; +import { fieldIndexComparer } from '../field/fieldComparers'; +import { getFieldDisplayName } from '../field/fieldState'; import { DataFrame, Field, @@ -17,15 +20,13 @@ import { TIME_SERIES_VALUE_FIELD_NAME, TIME_SERIES_TIME_FIELD_NAME, } from '../types/index'; -import { isDateTime } from '../datetime/moment_wrapper'; import { ArrayVector } from '../vector/ArrayVector'; -import { MutableDataFrame } from './MutableDataFrame'; import { SortedVector } from '../vector/SortedVector'; -import { ArrayDataFrame } from './ArrayDataFrame'; -import { getFieldDisplayName } from '../field/fieldState'; -import { fieldIndexComparer } from '../field/fieldComparers'; import { vectorToArray } from '../vector/vectorToArray'; + +import { ArrayDataFrame } from './ArrayDataFrame'; import { dataFrameFromJSON } from './DataFrameJSON'; +import { MutableDataFrame } from './MutableDataFrame'; function convertTableToDataFrame(table: TableData): DataFrame { const fields = table.columns.map((c) => { diff --git a/packages/grafana-data/src/dataframe/utils.test.ts b/packages/grafana-data/src/dataframe/utils.test.ts index 943a1d900bf..b291b732fd4 100644 --- a/packages/grafana-data/src/dataframe/utils.test.ts +++ b/packages/grafana-data/src/dataframe/utils.test.ts @@ -1,5 +1,6 @@ -import { toDataFrame } from './processDataFrame'; import { FieldType } from '../types'; + +import { toDataFrame } from './processDataFrame'; import { anySeriesWithTimeField } from './utils'; describe('anySeriesWithTimeField', () => { diff --git a/packages/grafana-data/src/dataframe/utils.ts b/packages/grafana-data/src/dataframe/utils.ts index 4044393d839..0869d2ec135 100644 --- a/packages/grafana-data/src/dataframe/utils.ts +++ b/packages/grafana-data/src/dataframe/utils.ts @@ -1,4 +1,5 @@ import { DataFrame, FieldType } from '../types/dataFrame'; + import { getTimeField } from './processDataFrame'; export function isTimeSerie(frame: DataFrame) { diff --git a/packages/grafana-data/src/datetime/datemath.test.ts b/packages/grafana-data/src/datetime/datemath.test.ts index 6033fc3530a..d644f3be4a1 100644 --- a/packages/grafana-data/src/datetime/datemath.test.ts +++ b/packages/grafana-data/src/datetime/datemath.test.ts @@ -1,5 +1,5 @@ -import sinon, { SinonFakeTimers } from 'sinon'; import { each } from 'lodash'; +import sinon, { SinonFakeTimers } from 'sinon'; import * as dateMath from './datemath'; import { dateTime, DurationUnit, DateTime } from './moment_wrapper'; diff --git a/packages/grafana-data/src/datetime/datemath.ts b/packages/grafana-data/src/datetime/datemath.ts index a14aceeb7e9..5478aa53edf 100644 --- a/packages/grafana-data/src/datetime/datemath.ts +++ b/packages/grafana-data/src/datetime/datemath.ts @@ -1,7 +1,9 @@ import { includes, isDate } from 'lodash'; -import { DateTime, dateTime, dateTimeForTimeZone, DurationUnit, isDateTime, ISO_8601 } from './moment_wrapper'; + import { TimeZone } from '../types/index'; +import { DateTime, dateTime, dateTimeForTimeZone, DurationUnit, isDateTime, ISO_8601 } from './moment_wrapper'; + const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'Q']; /** diff --git a/packages/grafana-data/src/datetime/durationutil.ts b/packages/grafana-data/src/datetime/durationutil.ts index 73185a2c29b..372144fd6cf 100644 --- a/packages/grafana-data/src/datetime/durationutil.ts +++ b/packages/grafana-data/src/datetime/durationutil.ts @@ -1,6 +1,6 @@ import { Duration, Interval } from 'date-fns'; -import intervalToDuration from 'date-fns/intervalToDuration'; import add from 'date-fns/add'; +import intervalToDuration from 'date-fns/intervalToDuration'; const durationMap: { [key in Required]: string[] } = { years: ['y', 'Y', 'years'], diff --git a/packages/grafana-data/src/datetime/formatter.ts b/packages/grafana-data/src/datetime/formatter.ts index 108f0a7d9ff..2b084ab62ca 100644 --- a/packages/grafana-data/src/datetime/formatter.ts +++ b/packages/grafana-data/src/datetime/formatter.ts @@ -1,9 +1,11 @@ /* eslint-disable id-blacklist, no-restricted-imports, @typescript-eslint/ban-types */ import moment, { MomentInput, Moment } from 'moment-timezone'; + import { TimeZone } from '../types'; -import { DateTimeInput } from './moment_wrapper'; -import { systemDateFormats } from './formats'; + import { DateTimeOptions, getTimeZone } from './common'; +import { systemDateFormats } from './formats'; +import { DateTimeInput } from './moment_wrapper'; /** * The type describing the options that can be passed to the {@link dateTimeFormat} diff --git a/packages/grafana-data/src/datetime/moment_wrapper.ts b/packages/grafana-data/src/datetime/moment_wrapper.ts index 80af4900f8e..c22b045d796 100644 --- a/packages/grafana-data/src/datetime/moment_wrapper.ts +++ b/packages/grafana-data/src/datetime/moment_wrapper.ts @@ -1,6 +1,7 @@ +import moment, { Moment, MomentInput, DurationInputArg1, DurationInputArg2 } from 'moment'; + import { TimeZone } from '../types/time'; /* eslint-disable id-blacklist, no-restricted-imports, @typescript-eslint/ban-types */ -import moment, { Moment, MomentInput, DurationInputArg1, DurationInputArg2 } from 'moment'; export interface DateTimeBuiltinFormat { __momentBuiltinFormatBrand: any; } diff --git a/packages/grafana-data/src/datetime/parser.test.ts b/packages/grafana-data/src/datetime/parser.test.ts index 62209df8315..ec9e216184c 100644 --- a/packages/grafana-data/src/datetime/parser.test.ts +++ b/packages/grafana-data/src/datetime/parser.test.ts @@ -1,5 +1,5 @@ -import { dateTimeParse } from './parser'; import { systemDateFormats } from './formats'; +import { dateTimeParse } from './parser'; describe('dateTimeParse', () => { it('should be able to parse using default format', () => { diff --git a/packages/grafana-data/src/datetime/parser.ts b/packages/grafana-data/src/datetime/parser.ts index 26901deaaee..9aaf6fcde1b 100644 --- a/packages/grafana-data/src/datetime/parser.ts +++ b/packages/grafana-data/src/datetime/parser.ts @@ -1,10 +1,11 @@ /* eslint-disable id-blacklist, no-restricted-imports, @typescript-eslint/ban-types */ +import { lowerCase } from 'lodash'; import moment, { MomentInput } from 'moment-timezone'; -import { DateTimeInput, DateTime, isDateTime } from './moment_wrapper'; + import { DateTimeOptions, getTimeZone } from './common'; import { parse, isValid } from './datemath'; -import { lowerCase } from 'lodash'; import { systemDateFormats } from './formats'; +import { DateTimeInput, DateTime, isDateTime } from './moment_wrapper'; /** * The type that describes options that can be passed when parsing a date and time value. diff --git a/packages/grafana-data/src/datetime/rangeutil.test.ts b/packages/grafana-data/src/datetime/rangeutil.test.ts index 9d3980fb35a..d3f376faefb 100644 --- a/packages/grafana-data/src/datetime/rangeutil.test.ts +++ b/packages/grafana-data/src/datetime/rangeutil.test.ts @@ -1,7 +1,9 @@ import { TimeRange } from '../types/time'; -import { dateTime, rangeUtil } from './index'; + import { timeRangeToRelative } from './rangeutil'; +import { dateTime, rangeUtil } from './index'; + describe('Range Utils', () => { describe('relative time', () => { it('should identify absolute vs relative', () => { diff --git a/packages/grafana-data/src/datetime/rangeutil.ts b/packages/grafana-data/src/datetime/rangeutil.ts index 16e34be35ea..ec0dded1a86 100644 --- a/packages/grafana-data/src/datetime/rangeutil.ts +++ b/packages/grafana-data/src/datetime/rangeutil.ts @@ -3,8 +3,8 @@ import { each, has } from 'lodash'; import { RawTimeRange, TimeRange, TimeZone, IntervalValues, RelativeTimeRange, TimeOption } from '../types/time'; import * as dateMath from './datemath'; -import { isDateTime, DateTime, dateTime } from './moment_wrapper'; import { timeZoneAbbrevation, dateTimeFormat, dateTimeFormatTimeAgo } from './formatter'; +import { isDateTime, DateTime, dateTime } from './moment_wrapper'; import { dateTimeParse } from './parser'; const spans: { [key: string]: { display: string; section?: number } } = { diff --git a/packages/grafana-data/src/datetime/timezones.test.ts b/packages/grafana-data/src/datetime/timezones.test.ts index cd78b67940b..04e49cc90c2 100644 --- a/packages/grafana-data/src/datetime/timezones.test.ts +++ b/packages/grafana-data/src/datetime/timezones.test.ts @@ -1,5 +1,5 @@ -import { getTimeZoneInfo } from './timezones'; import { setTimeZoneResolver } from './common'; +import { getTimeZoneInfo } from './timezones'; describe('getTimeZoneInfo', () => { // global timezone is set to Pacific/Easter, see jest-config.js file diff --git a/packages/grafana-data/src/datetime/timezones.ts b/packages/grafana-data/src/datetime/timezones.ts index efd198474da..6241dd35f43 100644 --- a/packages/grafana-data/src/datetime/timezones.ts +++ b/packages/grafana-data/src/datetime/timezones.ts @@ -1,6 +1,8 @@ -import moment from 'moment-timezone'; import { memoize } from 'lodash'; +import moment from 'moment-timezone'; + import { TimeZone } from '../types'; + import { getTimeZone } from './common'; export enum InternalTimeZones { diff --git a/packages/grafana-data/src/events/EventBus.test.ts b/packages/grafana-data/src/events/EventBus.test.ts index e0b0ca34c15..be189e409c5 100644 --- a/packages/grafana-data/src/events/EventBus.test.ts +++ b/packages/grafana-data/src/events/EventBus.test.ts @@ -1,7 +1,7 @@ import { EventBusSrv } from './EventBus'; -import { BusEvent, BusEventWithPayload } from './types'; -import { eventFactory } from './eventFactory'; import { DataHoverEvent } from './common'; +import { eventFactory } from './eventFactory'; +import { BusEvent, BusEventWithPayload } from './types'; interface LoginEventPayload { logins: number; diff --git a/packages/grafana-data/src/events/EventBus.ts b/packages/grafana-data/src/events/EventBus.ts index 2dd3e7a55b2..ca498274da7 100644 --- a/packages/grafana-data/src/events/EventBus.ts +++ b/packages/grafana-data/src/events/EventBus.ts @@ -1,6 +1,7 @@ import EventEmitter from 'eventemitter3'; import { Unsubscribable, Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; + import { EventBus, LegacyEmitter, diff --git a/packages/grafana-data/src/events/common.ts b/packages/grafana-data/src/events/common.ts index 0bad1af0080..4ed44ff945a 100644 --- a/packages/grafana-data/src/events/common.ts +++ b/packages/grafana-data/src/events/common.ts @@ -1,4 +1,5 @@ import { AnnotationEvent, DataFrame } from '../types'; + import { BusEventBase, BusEventWithPayload } from './types'; /** diff --git a/packages/grafana-data/src/field/FieldConfigOptionsRegistry.tsx b/packages/grafana-data/src/field/FieldConfigOptionsRegistry.tsx index 9166a6bbda5..d1081ef0628 100644 --- a/packages/grafana-data/src/field/FieldConfigOptionsRegistry.tsx +++ b/packages/grafana-data/src/field/FieldConfigOptionsRegistry.tsx @@ -1,4 +1,4 @@ -import { Registry } from '../utils/Registry'; import { FieldConfigPropertyItem } from '../types/fieldOverrides'; +import { Registry } from '../utils/Registry'; export class FieldConfigOptionsRegistry extends Registry {} diff --git a/packages/grafana-data/src/field/displayProcessor.test.ts b/packages/grafana-data/src/field/displayProcessor.test.ts index f6e45e844d7..51767895d2c 100644 --- a/packages/grafana-data/src/field/displayProcessor.test.ts +++ b/packages/grafana-data/src/field/displayProcessor.test.ts @@ -1,9 +1,10 @@ -import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor'; -import { DisplayProcessor, DisplayValue } from '../types/displayValue'; -import { MappingType, ValueMapping } from '../types/valueMapping'; -import { FieldConfig, FieldType, ThresholdsMode } from '../types'; import { systemDateFormats } from '../datetime'; import { createTheme } from '../themes'; +import { FieldConfig, FieldType, ThresholdsMode } from '../types'; +import { DisplayProcessor, DisplayValue } from '../types/displayValue'; +import { MappingType, ValueMapping } from '../types/valueMapping'; + +import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor'; function getDisplayProcessorFromConfig(config: FieldConfig) { return getDisplayProcessor({ diff --git a/packages/grafana-data/src/field/displayProcessor.ts b/packages/grafana-data/src/field/displayProcessor.ts index c3cc793df99..f4a7a79f907 100644 --- a/packages/grafana-data/src/field/displayProcessor.ts +++ b/packages/grafana-data/src/field/displayProcessor.ts @@ -2,16 +2,17 @@ import { toString, toNumber as _toNumber, isEmpty, isBoolean, isArray, join } from 'lodash'; // Types +import { getFieldTypeFromValue } from '../dataframe/processDataFrame'; +import { toUtc, dateTimeParse } from '../datetime'; +import { GrafanaTheme2 } from '../themes/types'; +import { KeyValue, TimeZone } from '../types'; import { Field, FieldType } from '../types/dataFrame'; import { DisplayProcessor, DisplayValue } from '../types/displayValue'; -import { getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats'; -import { getValueMappingResult } from '../utils/valueMappings'; -import { toUtc, dateTimeParse } from '../datetime'; -import { KeyValue, TimeZone } from '../types'; -import { getScaleCalculator } from './scale'; -import { GrafanaTheme2 } from '../themes/types'; import { anyToNumber } from '../utils/anyToNumber'; -import { getFieldTypeFromValue } from '../dataframe/processDataFrame'; +import { getValueMappingResult } from '../utils/valueMappings'; +import { getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats'; + +import { getScaleCalculator } from './scale'; interface DisplayProcessorOptions { field: Partial; diff --git a/packages/grafana-data/src/field/fieldColor.test.ts b/packages/grafana-data/src/field/fieldColor.test.ts index 08eb1a0bbb7..46a708d0fcd 100644 --- a/packages/grafana-data/src/field/fieldColor.test.ts +++ b/packages/grafana-data/src/field/fieldColor.test.ts @@ -1,6 +1,7 @@ import { createTheme } from '../themes'; import { Field, FieldColorModeId, FieldType } from '../types'; import { ArrayVector } from '../vector/ArrayVector'; + import { fieldColorModeRegistry, FieldValueColorCalculator, getFieldSeriesColor } from './fieldColor'; function getTestField(mode: string): Field { diff --git a/packages/grafana-data/src/field/fieldColor.ts b/packages/grafana-data/src/field/fieldColor.ts index ffbdf95ac23..003b2e8c684 100644 --- a/packages/grafana-data/src/field/fieldColor.ts +++ b/packages/grafana-data/src/field/fieldColor.ts @@ -1,11 +1,13 @@ +import { interpolateRgbBasis } from 'd3-interpolate'; + +import { GrafanaTheme2 } from '../themes/types'; +import { reduceField } from '../transformations/fieldReducer'; import { FALLBACK_COLOR, Field, FieldColorModeId, Threshold } from '../types'; import { RegistryItem } from '../utils'; import { Registry } from '../utils/Registry'; -import { interpolateRgbBasis } from 'd3-interpolate'; -import { fallBackTreshold } from './thresholds'; + import { getScaleCalculator, ColorScaleValue } from './scale'; -import { reduceField } from '../transformations/fieldReducer'; -import { GrafanaTheme2 } from '../themes/types'; +import { fallBackTreshold } from './thresholds'; /** @beta */ export type FieldValueColorCalculator = (value: number, percent: number, Threshold?: Threshold) => string; diff --git a/packages/grafana-data/src/field/fieldComparers.ts b/packages/grafana-data/src/field/fieldComparers.ts index 1eab7665c67..3deb8f6c12e 100644 --- a/packages/grafana-data/src/field/fieldComparers.ts +++ b/packages/grafana-data/src/field/fieldComparers.ts @@ -1,7 +1,8 @@ +import { isNumber } from 'lodash'; + +import { dateTime } from '../datetime'; import { Field, FieldType } from '../types/dataFrame'; import { Vector } from '../types/vector'; -import { dateTime } from '../datetime'; -import { isNumber } from 'lodash'; type IndexComparer = (a: number, b: number) => number; diff --git a/packages/grafana-data/src/field/fieldDisplay.test.ts b/packages/grafana-data/src/field/fieldDisplay.test.ts index b36c909dfd2..2a8d91aed78 100644 --- a/packages/grafana-data/src/field/fieldDisplay.test.ts +++ b/packages/grafana-data/src/field/fieldDisplay.test.ts @@ -1,11 +1,13 @@ import { merge } from 'lodash'; -import { getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay'; + import { toDataFrame } from '../dataframe/processDataFrame'; +import { createTheme } from '../themes'; import { ReducerID } from '../transformations/fieldReducer'; import { MappingType, SpecialValueMatch, ValueMapping } from '../types'; -import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; -import { createTheme } from '../themes'; + import { getDisplayProcessor } from './displayProcessor'; +import { getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay'; +import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; describe('FieldDisplay', () => { beforeAll(() => { diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index c9bf243688a..ac4c8f8d7b9 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -1,6 +1,11 @@ import { toString, isEmpty } from 'lodash'; -import { getDisplayProcessor } from './displayProcessor'; +import { DataFrameView } from '../dataframe/DataFrameView'; +import { getTimeField } from '../dataframe/processDataFrame'; +import { GrafanaTheme2 } from '../themes'; +import { getFieldMatcher } from '../transformations'; +import { reduceField, ReducerID } from '../transformations/fieldReducer'; +import { FieldMatcherID } from '../transformations/matchers/ids'; import { DataFrame, DisplayValue, @@ -14,13 +19,9 @@ import { TimeRange, TimeZone, } from '../types'; -import { DataFrameView } from '../dataframe/DataFrameView'; -import { GrafanaTheme2 } from '../themes'; -import { reduceField, ReducerID } from '../transformations/fieldReducer'; import { ScopedVars } from '../types/ScopedVars'; -import { getTimeField } from '../dataframe/processDataFrame'; -import { getFieldMatcher } from '../transformations'; -import { FieldMatcherID } from '../transformations/matchers/ids'; + +import { getDisplayProcessor } from './displayProcessor'; import { getFieldDisplayName } from './fieldState'; /** diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 7a88ba7251a..41dc112fdc4 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -1,13 +1,6 @@ -import { - applyFieldOverrides, - applyRawFieldOverrides, - FieldOverrideEnv, - findNumericFieldMinMax, - getLinksSupplier, - setDynamicConfigValue, - setFieldConfigDefaults, -} from './fieldOverrides'; import { ArrayDataFrame, MutableDataFrame, toDataFrame } from '../dataframe'; +import { createTheme } from '../themes'; +import { FieldMatcherID } from '../transformations'; import { DataFrame, Field, @@ -22,12 +15,20 @@ import { } from '../types'; import { locationUtil, Registry } from '../utils'; import { mockStandardProperties } from '../utils/tests/mockStandardProperties'; -import { FieldMatcherID } from '../transformations'; -import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; -import { getFieldDisplayName } from './fieldState'; import { ArrayVector } from '../vector'; + +import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; import { getDisplayProcessor } from './displayProcessor'; -import { createTheme } from '../themes'; +import { + applyFieldOverrides, + applyRawFieldOverrides, + FieldOverrideEnv, + findNumericFieldMinMax, + getLinksSupplier, + setDynamicConfigValue, + setFieldConfigDefaults, +} from './fieldOverrides'; +import { getFieldDisplayName } from './fieldState'; const property1: any = { id: 'custom.property1', // Match field properties diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index d6358395d80..e656aeea158 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -1,6 +1,13 @@ +import { isNumber, set, unset, get, cloneDeep } from 'lodash'; + +import { guessFieldTypeForField } from '../dataframe'; +import { getTimeField } from '../dataframe/processDataFrame'; +import { asHexString } from '../themes/colorManipulator'; +import { fieldMatchers, reduceField, ReducerID } from '../transformations'; import { ApplyFieldOverrideOptions, DataFrame, + DataLink, DisplayProcessor, DisplayValue, DynamicConfigValue, @@ -17,21 +24,17 @@ import { TimeZone, ValueLinkConfig, } from '../types'; -import { fieldMatchers, reduceField, ReducerID } from '../transformations'; import { FieldMatcher } from '../types/transformations'; -import { isNumber, set, unset, get, cloneDeep } from 'lodash'; -import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor'; -import { guessFieldTypeForField } from '../dataframe'; -import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; -import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; import { DataLinkBuiltInVars, locationUtil } from '../utils'; -import { formattedValueToString } from '../valueFormats'; -import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy'; -import { getFrameDisplayName } from './fieldState'; -import { getTimeField } from '../dataframe/processDataFrame'; import { mapInternalLinkToExplore } from '../utils/dataLinks'; +import { formattedValueToString } from '../valueFormats'; + +import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; +import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor'; +import { getFrameDisplayName } from './fieldState'; +import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy'; +import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; import { getTemplateProxyForField } from './templateProxies'; -import { asHexString } from '../themes/colorManipulator'; interface OverrideProps { match: FieldMatcher; @@ -352,7 +355,7 @@ export const getLinksSupplier = const timeRangeUrl = locationUtil.getTimeRangeUrlParams(); const { timeField } = getTimeField(frame); - return field.config.links.map((link) => { + return field.config.links.map((link: DataLink) => { const variablesQuery = locationUtil.getVariablesUrlParams(); let dataFrameVars = {}; let valueVars = {}; @@ -438,7 +441,7 @@ export const getLinksSupplier = } let href = locationUtil.assureBaseUrl(link.url.replace(/\n/g, '')); - href = replaceVariables(href, variables, encodeURIComponent); + href = replaceVariables(href, variables); href = locationUtil.processUrl(href); const info: LinkModel = { diff --git a/packages/grafana-data/src/field/fieldState.test.ts b/packages/grafana-data/src/field/fieldState.test.ts index 656016fb9b3..8be0a7d4ad3 100644 --- a/packages/grafana-data/src/field/fieldState.test.ts +++ b/packages/grafana-data/src/field/fieldState.test.ts @@ -1,6 +1,7 @@ -import { DataFrame, TIME_SERIES_VALUE_FIELD_NAME, FieldType } from '../types'; -import { getFieldDisplayName, getFrameDisplayName } from './fieldState'; import { toDataFrame } from '../dataframe'; +import { DataFrame, TIME_SERIES_VALUE_FIELD_NAME, FieldType } from '../types'; + +import { getFieldDisplayName, getFrameDisplayName } from './fieldState'; interface TitleScenario { frames: DataFrame[]; diff --git a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx index 0efbb4e1ffd..92b9181fe72 100644 --- a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx +++ b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx @@ -1,8 +1,9 @@ -import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy'; -import { applyFieldOverrides } from './fieldOverrides'; import { MutableDataFrame, toDataFrame } from '../dataframe'; import { createTheme } from '../themes'; +import { applyFieldOverrides } from './fieldOverrides'; +import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy'; + describe('getFieldDisplayValuesProxy', () => { const shortTimeField = [{ name: 'Time', values: [1, 2, 3] }]; diff --git a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts index 4faec716e4a..aaeeb81012a 100644 --- a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts +++ b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.ts @@ -1,6 +1,8 @@ import { toNumber } from 'lodash'; + import { DataFrame, DisplayValue, TimeZone } from '../types'; import { formattedValueToString } from '../valueFormats'; + import { getDisplayProcessor } from './displayProcessor'; /** diff --git a/packages/grafana-data/src/field/scale.test.ts b/packages/grafana-data/src/field/scale.test.ts index da8340a4bef..9cfd9dbb1ce 100644 --- a/packages/grafana-data/src/field/scale.test.ts +++ b/packages/grafana-data/src/field/scale.test.ts @@ -1,9 +1,10 @@ -import { ThresholdsMode, Field, FieldType, FieldColorModeId } from '../types'; -import { sortThresholds } from './thresholds'; -import { ArrayVector } from '../vector/ArrayVector'; -import { getScaleCalculator } from './scale'; import { createTheme } from '../themes'; +import { ThresholdsMode, Field, FieldType, FieldColorModeId } from '../types'; import { getColorForTheme } from '../utils'; +import { ArrayVector } from '../vector/ArrayVector'; + +import { getScaleCalculator } from './scale'; +import { sortThresholds } from './thresholds'; describe('getScaleCalculator', () => { it('should return percent, threshold and color', () => { diff --git a/packages/grafana-data/src/field/scale.ts b/packages/grafana-data/src/field/scale.ts index 822348edeb9..e97b01faf94 100644 --- a/packages/grafana-data/src/field/scale.ts +++ b/packages/grafana-data/src/field/scale.ts @@ -1,7 +1,9 @@ import { isNumber } from 'lodash'; + import { GrafanaTheme2 } from '../themes/types'; import { reduceField, ReducerID } from '../transformations/fieldReducer'; import { Field, FieldConfig, FieldType, NumericRange, Threshold } from '../types'; + import { getFieldColorModeForField } from './fieldColor'; import { getActiveThresholdForValue } from './thresholds'; diff --git a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts index 9bc019c5fcb..f99d8decb8e 100644 --- a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts +++ b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts @@ -1,8 +1,10 @@ -import { Registry, RegistryItem } from '../utils/Registry'; import { ComponentType } from 'react'; -import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; -import { DataFrame, InterpolateFunction, VariableSuggestionsScope, VariableSuggestion } from '../types'; + import { EventBus } from '../events'; +import { DataFrame, InterpolateFunction, VariableSuggestionsScope, VariableSuggestion } from '../types'; +import { Registry, RegistryItem } from '../utils/Registry'; + +import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry'; export interface StandardEditorContext { data: DataFrame[]; // All results diff --git a/packages/grafana-data/src/field/templateProxies.test.ts b/packages/grafana-data/src/field/templateProxies.test.ts index d1ea6745aa2..154bd77628d 100644 --- a/packages/grafana-data/src/field/templateProxies.test.ts +++ b/packages/grafana-data/src/field/templateProxies.test.ts @@ -1,6 +1,7 @@ -import { getTemplateProxyForField } from './templateProxies'; import { toDataFrame } from '../dataframe'; +import { getTemplateProxyForField } from './templateProxies'; + describe('Template proxies', () => { it('supports name and displayName', () => { const frames = [ diff --git a/packages/grafana-data/src/field/templateProxies.ts b/packages/grafana-data/src/field/templateProxies.ts index c667ed874cb..28d553ec4f7 100644 --- a/packages/grafana-data/src/field/templateProxies.ts +++ b/packages/grafana-data/src/field/templateProxies.ts @@ -1,7 +1,8 @@ import { DataFrame, Field } from '../types'; -import { getFieldDisplayName } from './fieldState'; import { formatLabels } from '../utils/labels'; +import { getFieldDisplayName } from './fieldState'; + /** * This object is created often, and only used when tmplates exist. Using a proxy lets us delay * calculations of the more complex structures (label names) until they are actually used diff --git a/packages/grafana-data/src/field/thresholds.test.ts b/packages/grafana-data/src/field/thresholds.test.ts index 52b232b9346..500aae9170a 100644 --- a/packages/grafana-data/src/field/thresholds.test.ts +++ b/packages/grafana-data/src/field/thresholds.test.ts @@ -1,8 +1,9 @@ import { ThresholdsConfig, ThresholdsMode, FieldConfig, Threshold, Field, FieldType } from '../types'; -import { sortThresholds, getActiveThreshold, getActiveThresholdForValue } from './thresholds'; -import { validateFieldConfig } from './fieldOverrides'; import { ArrayVector } from '../vector/ArrayVector'; +import { validateFieldConfig } from './fieldOverrides'; +import { sortThresholds, getActiveThreshold, getActiveThresholdForValue } from './thresholds'; + describe('thresholds', () => { test('sort thresholds', () => { const thresholds: ThresholdsConfig = { diff --git a/packages/grafana-data/src/geo/layer.ts b/packages/grafana-data/src/geo/layer.ts index 88c22cda757..60463aa7ebc 100644 --- a/packages/grafana-data/src/geo/layer.ts +++ b/packages/grafana-data/src/geo/layer.ts @@ -1,10 +1,11 @@ -import { RegistryItemWithOptions } from '../utils/Registry'; -import { PanelData } from '../types'; -import { GrafanaTheme2 } from '../themes'; -import { PanelOptionsEditorBuilder } from '../utils'; -import { ReactNode } from 'react'; import { PluggableMap } from 'ol'; import BaseLayer from 'ol/layer/Base'; +import { ReactNode } from 'react'; + +import { GrafanaTheme2 } from '../themes'; +import { PanelData } from '../types'; +import { PanelOptionsEditorBuilder } from '../utils'; +import { RegistryItemWithOptions } from '../utils/Registry'; /** * @alpha diff --git a/packages/grafana-data/src/panel/PanelPlugin.test.tsx b/packages/grafana-data/src/panel/PanelPlugin.test.tsx index ad403c73779..074b60db072 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.test.tsx +++ b/packages/grafana-data/src/panel/PanelPlugin.test.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import { identityOverrideProcessor, standardEditorsRegistry, standardFieldConfigEditorRegistry } from '../field'; -import { PanelPlugin } from './PanelPlugin'; -import { FieldConfigProperty } from '../types'; + import { PanelOptionsEditorBuilder } from '..'; +import { identityOverrideProcessor, standardEditorsRegistry, standardFieldConfigEditorRegistry } from '../field'; +import { FieldConfigProperty } from '../types'; + +import { PanelPlugin } from './PanelPlugin'; describe('PanelPlugin', () => { describe('declarative options', () => { diff --git a/packages/grafana-data/src/panel/PanelPlugin.ts b/packages/grafana-data/src/panel/PanelPlugin.ts index a2efd5cb665..dfbb009efdc 100644 --- a/packages/grafana-data/src/panel/PanelPlugin.ts +++ b/packages/grafana-data/src/panel/PanelPlugin.ts @@ -1,3 +1,7 @@ +import { set } from 'lodash'; +import { ComponentClass, ComponentType } from 'react'; + +import { FieldConfigOptionsRegistry, StandardEditorContext } from '../field'; import { FieldConfigSource, GrafanaPlugin, @@ -10,11 +14,9 @@ import { PanelPluginDataSupport, VisualizationSuggestionsSupplier, } from '../types'; -import { FieldConfigEditorBuilder, PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders'; -import { ComponentClass, ComponentType } from 'react'; -import { set } from 'lodash'; import { deprecationWarning } from '../utils'; -import { FieldConfigOptionsRegistry, StandardEditorContext } from '../field'; +import { FieldConfigEditorBuilder, PanelOptionsEditorBuilder } from '../utils/OptionsUIBuilders'; + import { createFieldConfigRegistry } from './registryFactories'; /** @beta */ diff --git a/packages/grafana-data/src/panel/registryFactories.ts b/packages/grafana-data/src/panel/registryFactories.ts index df811a35e20..0e007bc8ad0 100644 --- a/packages/grafana-data/src/panel/registryFactories.ts +++ b/packages/grafana-data/src/panel/registryFactories.ts @@ -2,6 +2,7 @@ import { FieldConfigOptionsRegistry } from '../field/FieldConfigOptionsRegistry' import { standardFieldConfigEditorRegistry } from '../field/standardFieldConfigEditorRegistry'; import { FieldConfigProperty, FieldConfigPropertyItem } from '../types/fieldOverrides'; import { FieldConfigEditorBuilder } from '../utils/OptionsUIBuilders'; + import { SetFieldConfigOptionsArgs } from './PanelPlugin'; /** diff --git a/packages/grafana-data/src/text/markdown.ts b/packages/grafana-data/src/text/markdown.ts index 5335d47145d..c93173828ff 100644 --- a/packages/grafana-data/src/text/markdown.ts +++ b/packages/grafana-data/src/text/markdown.ts @@ -1,4 +1,5 @@ import { marked } from 'marked'; + import { sanitize, sanitizeTextPanelContent } from './sanitize'; let hasInitialized = false; diff --git a/packages/grafana-data/src/text/sanitize.ts b/packages/grafana-data/src/text/sanitize.ts index 4a298f88db7..1539e83ed4d 100644 --- a/packages/grafana-data/src/text/sanitize.ts +++ b/packages/grafana-data/src/text/sanitize.ts @@ -1,5 +1,5 @@ -import * as xss from 'xss'; import { sanitizeUrl as braintreeSanitizeUrl } from '@braintree/sanitize-url'; +import * as xss from 'xss'; const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => { acc[element] = xss.whiteList[element]?.concat(['class', 'style']); diff --git a/packages/grafana-data/src/themes/createColors.ts b/packages/grafana-data/src/themes/createColors.ts index c98098d54f8..134c947721a 100644 --- a/packages/grafana-data/src/themes/createColors.ts +++ b/packages/grafana-data/src/themes/createColors.ts @@ -1,4 +1,5 @@ import { merge } from 'lodash'; + import { alpha, darken, emphasize, getContrastRatio, lighten } from './colorManipulator'; import { palette } from './palette'; import { DeepPartial, ThemeRichColor } from './types'; diff --git a/packages/grafana-data/src/themes/createComponents.ts b/packages/grafana-data/src/themes/createComponents.ts index fefd3dbf101..51603f036c7 100644 --- a/packages/grafana-data/src/themes/createComponents.ts +++ b/packages/grafana-data/src/themes/createComponents.ts @@ -39,6 +39,12 @@ export interface ThemeComponents { sidemenu: { width: number; }; + menuTabs: { + height: number; + }; + horizontalDrawer: { + defaultHeight: number; + }; } export function createComponents(colors: ThemeColors, shadows: ThemeShadows): ThemeComponents { @@ -82,5 +88,11 @@ export function createComponents(colors: ThemeColors, shadows: ThemeShadows): Th sidemenu: { width: 48, }, + menuTabs: { + height: 41, + }, + horizontalDrawer: { + defaultHeight: 400, + }, }; } diff --git a/packages/grafana-data/src/themes/createTheme.ts b/packages/grafana-data/src/themes/createTheme.ts index 7b528b1bc9a..b445c170bb5 100644 --- a/packages/grafana-data/src/themes/createTheme.ts +++ b/packages/grafana-data/src/themes/createTheme.ts @@ -1,15 +1,15 @@ import { createBreakpoints } from './breakpoints'; -import { createComponents } from './createComponents'; import { createColors, ThemeColorsInput } from './createColors'; +import { createComponents } from './createComponents'; import { createShadows } from './createShadows'; import { createShape, ThemeShapeInput } from './createShape'; import { createSpacing, ThemeSpacingOptions } from './createSpacing'; import { createTransitions } from './createTransitions'; import { createTypography, ThemeTypographyInput } from './createTypography'; import { createV1Theme } from './createV1Theme'; +import { createVisualizationColors } from './createVisualizationColors'; import { GrafanaTheme2 } from './types'; import { zIndex } from './zIndex'; -import { createVisualizationColors } from './createVisualizationColors'; /** @internal */ export interface NewThemeOptions { diff --git a/packages/grafana-data/src/themes/createV1Theme.ts b/packages/grafana-data/src/themes/createV1Theme.ts index aacff43a32b..a4754db37e6 100644 --- a/packages/grafana-data/src/themes/createV1Theme.ts +++ b/packages/grafana-data/src/themes/createV1Theme.ts @@ -1,4 +1,5 @@ import { GrafanaTheme, GrafanaThemeCommons, GrafanaThemeType } from '../types'; + import { GrafanaTheme2 } from './types'; export function createV1Theme(theme: Omit): GrafanaTheme { diff --git a/packages/grafana-data/src/themes/createVisualizationColors.ts b/packages/grafana-data/src/themes/createVisualizationColors.ts index 3a9a442180a..258cbe887be 100644 --- a/packages/grafana-data/src/themes/createVisualizationColors.ts +++ b/packages/grafana-data/src/themes/createVisualizationColors.ts @@ -1,4 +1,5 @@ import { FALLBACK_COLOR } from '../types'; + import { ThemeColors } from './createColors'; /** diff --git a/packages/grafana-data/src/themes/types.ts b/packages/grafana-data/src/themes/types.ts index d9b4595154c..0857a5da886 100644 --- a/packages/grafana-data/src/themes/types.ts +++ b/packages/grafana-data/src/themes/types.ts @@ -1,14 +1,15 @@ import { GrafanaTheme } from '../types/theme'; + import { ThemeBreakpoints } from './breakpoints'; -import { ThemeComponents } from './createComponents'; import { ThemeColors } from './createColors'; +import { ThemeComponents } from './createComponents'; import { ThemeShadows } from './createShadows'; import { ThemeShape } from './createShape'; import { ThemeSpacing } from './createSpacing'; import { ThemeTransitions } from './createTransitions'; import { ThemeTypography } from './createTypography'; -import { ThemeZIndices } from './zIndex'; import { ThemeVisualizationColors } from './createVisualizationColors'; +import { ThemeZIndices } from './zIndex'; /** * @beta diff --git a/packages/grafana-data/src/transformations/fieldReducer.test.ts b/packages/grafana-data/src/transformations/fieldReducer.test.ts index ec22d69f552..243479b7f2d 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.test.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.test.ts @@ -1,12 +1,12 @@ import { difference } from 'lodash'; -import { fieldReducers, ReducerID, reduceField } from './fieldReducer'; - -import { Field, FieldType } from '../types/index'; -import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; import { MutableDataFrame } from '../dataframe/MutableDataFrame'; +import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; +import { Field, FieldType } from '../types/index'; import { ArrayVector } from '../vector/ArrayVector'; +import { fieldReducers, ReducerID, reduceField } from './fieldReducer'; + /** * Run a reducer and get back the value */ diff --git a/packages/grafana-data/src/transformations/matchers.ts b/packages/grafana-data/src/transformations/matchers.ts index aa5e6c7c8b2..603fdcbca2d 100644 --- a/packages/grafana-data/src/transformations/matchers.ts +++ b/packages/grafana-data/src/transformations/matchers.ts @@ -1,8 +1,4 @@ // Load the Builtin matchers -import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates'; -import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher'; -import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher'; -import { getRefIdMatchers } from './matchers/refIdMatcher'; import { FieldMatcherInfo, MatcherConfig, @@ -13,11 +9,16 @@ import { ValueMatcher, } from '../types/transformations'; import { Registry } from '../utils/Registry'; + +import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher'; +import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher'; +import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates'; +import { getRefIdMatchers } from './matchers/refIdMatcher'; +import { getSimpleFieldMatchers } from './matchers/simpleFieldMatcher'; +import { getEqualValueMatchers } from './matchers/valueMatchers/equalMatchers'; import { getNullValueMatchers } from './matchers/valueMatchers/nullMatchers'; import { getNumericValueMatchers } from './matchers/valueMatchers/numericMatchers'; -import { getEqualValueMatchers } from './matchers/valueMatchers/equalMatchers'; import { getRangeValueMatchers } from './matchers/valueMatchers/rangeMatchers'; -import { getSimpleFieldMatchers } from './matchers/simpleFieldMatcher'; import { getRegexValueMatcher } from './matchers/valueMatchers/regexMatchers'; /** diff --git a/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.test.ts index c6370b0ba8e..af055205159 100644 --- a/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.test.ts @@ -1,7 +1,8 @@ +import { toDataFrame } from '../../dataframe/processDataFrame'; import { FieldType } from '../../types/dataFrame'; import { fieldMatchers } from '../matchers'; + import { FieldMatcherID } from './ids'; -import { toDataFrame } from '../../dataframe/processDataFrame'; export const simpleSeriesWithTypes = toDataFrame({ fields: [ diff --git a/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.ts b/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.ts index 4cb8201c08b..23967cad334 100644 --- a/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.ts @@ -1,7 +1,8 @@ import { Field, FieldType, DataFrame } from '../../types/dataFrame'; -import { FieldMatcherID } from './ids'; import { FieldMatcherInfo } from '../../types/transformations'; +import { FieldMatcherID } from './ids'; + // General Field matcher const fieldTypeMatcher: FieldMatcherInfo = { id: FieldMatcherID.byType, diff --git a/packages/grafana-data/src/transformations/matchers/matchers.test.ts b/packages/grafana-data/src/transformations/matchers/matchers.test.ts index 81ea76b2b75..5ba48c3f3b2 100644 --- a/packages/grafana-data/src/transformations/matchers/matchers.test.ts +++ b/packages/grafana-data/src/transformations/matchers/matchers.test.ts @@ -1,4 +1,5 @@ import { fieldMatchers } from '../matchers'; + import { FieldMatcherID } from './ids'; describe('Matchers', () => { diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts index 1ca7c454166..df611f50bcb 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts @@ -1,6 +1,7 @@ -import { getFieldMatcher } from '../matchers'; -import { FieldMatcherID } from './ids'; import { toDataFrame } from '../../dataframe/processDataFrame'; +import { getFieldMatcher } from '../matchers'; + +import { FieldMatcherID } from './ids'; import { ByNamesMatcherMode } from './nameMatcher'; describe('Field Name by Regexp Matcher', () => { diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts index 2f7f6b34687..45f3e75606b 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts @@ -1,8 +1,9 @@ -import { Field, DataFrame } from '../../types/dataFrame'; -import { FieldMatcherID, FrameMatcherID } from './ids'; -import { FieldMatcherInfo, FrameMatcherInfo, FieldMatcher } from '../../types/transformations'; -import { stringToJsRegex } from '../../text/string'; import { getFieldDisplayName } from '../../field/fieldState'; +import { stringToJsRegex } from '../../text/string'; +import { Field, DataFrame } from '../../types/dataFrame'; +import { FieldMatcherInfo, FrameMatcherInfo, FieldMatcher } from '../../types/transformations'; + +import { FieldMatcherID, FrameMatcherID } from './ids'; export interface RegexpOrNamesMatcherOptions { pattern?: string; diff --git a/packages/grafana-data/src/transformations/matchers/predicates.test.ts b/packages/grafana-data/src/transformations/matchers/predicates.test.ts index ef007b5dc28..708d9aa8b9f 100644 --- a/packages/grafana-data/src/transformations/matchers/predicates.test.ts +++ b/packages/grafana-data/src/transformations/matchers/predicates.test.ts @@ -1,8 +1,9 @@ import { FieldType } from '../../types/dataFrame'; +import { MatcherConfig } from '../../types/transformations'; import { fieldMatchers } from '../matchers'; + import { simpleSeriesWithTypes } from './fieldTypeMatcher.test'; import { FieldMatcherID, MatcherID } from './ids'; -import { MatcherConfig } from '../../types/transformations'; const matchesNumberConfig: MatcherConfig = { id: FieldMatcherID.byType, diff --git a/packages/grafana-data/src/transformations/matchers/predicates.ts b/packages/grafana-data/src/transformations/matchers/predicates.ts index cf0b2a90ded..386d87b69fe 100644 --- a/packages/grafana-data/src/transformations/matchers/predicates.ts +++ b/packages/grafana-data/src/transformations/matchers/predicates.ts @@ -1,7 +1,8 @@ import { Field, DataFrame, FieldType } from '../../types/dataFrame'; -import { MatcherID } from './ids'; -import { getFieldMatcher, fieldMatchers, getFrameMatchers, frameMatchers } from '../matchers'; import { FieldMatcherInfo, MatcherConfig, FrameMatcherInfo } from '../../types/transformations'; +import { getFieldMatcher, fieldMatchers, getFrameMatchers, frameMatchers } from '../matchers'; + +import { MatcherID } from './ids'; const anyFieldMatcher: FieldMatcherInfo = { id: MatcherID.anyMatch, diff --git a/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts b/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts index a8e0aa99ede..1c9d5d5869d 100644 --- a/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts @@ -1,7 +1,8 @@ -import { DataFrame } from '../../types/dataFrame'; -import { FrameMatcherID } from './ids'; -import { FrameMatcherInfo } from '../../types/transformations'; import { stringToJsRegex } from '../../text'; +import { DataFrame } from '../../types/dataFrame'; +import { FrameMatcherInfo } from '../../types/transformations'; + +import { FrameMatcherID } from './ids'; // General Field matcher const refIdMacher: FrameMatcherInfo = { diff --git a/packages/grafana-data/src/transformations/matchers/simpleFieldMatcher.ts b/packages/grafana-data/src/transformations/matchers/simpleFieldMatcher.ts index b32dfaf715c..70899669774 100644 --- a/packages/grafana-data/src/transformations/matchers/simpleFieldMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/simpleFieldMatcher.ts @@ -1,7 +1,8 @@ import { Field, FieldType, DataFrame } from '../../types/dataFrame'; -import { FieldMatcherID } from './ids'; import { FieldMatcherInfo } from '../../types/transformations'; +import { FieldMatcherID } from './ids'; + const firstFieldMatcher: FieldMatcherInfo = { id: FieldMatcherID.first, name: 'First Field', diff --git a/packages/grafana-data/src/transformations/matchers/valueMatchers/equalMatchers.ts b/packages/grafana-data/src/transformations/matchers/valueMatchers/equalMatchers.ts index c6b3deacdd4..3b9e4c3721a 100644 --- a/packages/grafana-data/src/transformations/matchers/valueMatchers/equalMatchers.ts +++ b/packages/grafana-data/src/transformations/matchers/valueMatchers/equalMatchers.ts @@ -1,6 +1,7 @@ import { Field } from '../../../types/dataFrame'; import { ValueMatcherInfo } from '../../../types/transformations'; import { ValueMatcherID } from '../ids'; + import { BasicValueMatcherOptions } from './types'; const isEqualValueMatcher: ValueMatcherInfo = { diff --git a/packages/grafana-data/src/transformations/matchers/valueMatchers/nullMatchers.ts b/packages/grafana-data/src/transformations/matchers/valueMatchers/nullMatchers.ts index e3f19a68d97..10e78201dbf 100644 --- a/packages/grafana-data/src/transformations/matchers/valueMatchers/nullMatchers.ts +++ b/packages/grafana-data/src/transformations/matchers/valueMatchers/nullMatchers.ts @@ -1,6 +1,7 @@ import { Field } from '../../../types/dataFrame'; import { ValueMatcherInfo } from '../../../types/transformations'; import { ValueMatcherID } from '../ids'; + import { ValueMatcherOptions } from './types'; const isNullValueMatcher: ValueMatcherInfo = { diff --git a/packages/grafana-data/src/transformations/matchers/valueMatchers/numericMatchers.ts b/packages/grafana-data/src/transformations/matchers/valueMatchers/numericMatchers.ts index 339cb0554dc..e2f24e6d98f 100644 --- a/packages/grafana-data/src/transformations/matchers/valueMatchers/numericMatchers.ts +++ b/packages/grafana-data/src/transformations/matchers/valueMatchers/numericMatchers.ts @@ -1,6 +1,7 @@ import { Field, FieldType } from '../../../types/dataFrame'; import { ValueMatcherInfo } from '../../../types/transformations'; import { ValueMatcherID } from '../ids'; + import { BasicValueMatcherOptions } from './types'; const isGreaterValueMatcher: ValueMatcherInfo> = { diff --git a/packages/grafana-data/src/transformations/matchers/valueMatchers/rangeMatchers.ts b/packages/grafana-data/src/transformations/matchers/valueMatchers/rangeMatchers.ts index 17edb0d4d41..45c438c1830 100644 --- a/packages/grafana-data/src/transformations/matchers/valueMatchers/rangeMatchers.ts +++ b/packages/grafana-data/src/transformations/matchers/valueMatchers/rangeMatchers.ts @@ -1,6 +1,7 @@ import { Field, FieldType } from '../../../types/dataFrame'; import { ValueMatcherInfo } from '../../../types/transformations'; import { ValueMatcherID } from '../ids'; + import { RangeValueMatcherOptions } from './types'; const isBetweenValueMatcher: ValueMatcherInfo> = { diff --git a/packages/grafana-data/src/transformations/matchers/valueMatchers/regexMatchers.ts b/packages/grafana-data/src/transformations/matchers/valueMatchers/regexMatchers.ts index b4b1590fd4b..92ec6582f33 100644 --- a/packages/grafana-data/src/transformations/matchers/valueMatchers/regexMatchers.ts +++ b/packages/grafana-data/src/transformations/matchers/valueMatchers/regexMatchers.ts @@ -1,6 +1,7 @@ import { Field } from '../../../types/dataFrame'; import { ValueMatcherInfo } from '../../../types/transformations'; import { ValueMatcherID } from '../ids'; + import { BasicValueMatcherOptions } from './types'; const regexValueMatcher: ValueMatcherInfo> = { diff --git a/packages/grafana-data/src/transformations/standardTransformersRegistry.ts b/packages/grafana-data/src/transformations/standardTransformersRegistry.ts index dc9fbaca85f..73d79a351ba 100644 --- a/packages/grafana-data/src/transformations/standardTransformersRegistry.ts +++ b/packages/grafana-data/src/transformations/standardTransformersRegistry.ts @@ -1,4 +1,5 @@ import React from 'react'; + import { DataFrame, DataTransformerInfo } from '../types'; import { Registry, RegistryItem } from '../utils/Registry'; diff --git a/packages/grafana-data/src/transformations/transformDataFrame.test.ts b/packages/grafana-data/src/transformations/transformDataFrame.test.ts index 6fefc2ae139..08412b09b68 100644 --- a/packages/grafana-data/src/transformations/transformDataFrame.test.ts +++ b/packages/grafana-data/src/transformations/transformDataFrame.test.ts @@ -1,11 +1,12 @@ -import { ReducerID } from './fieldReducer'; -import { DataTransformerID } from './transformers/ids'; import { toDataFrame } from '../dataframe/processDataFrame'; -import { mockTransformationsRegistry } from '../utils/tests/mockTransformationsRegistry'; -import { reduceTransformer } from './transformers/reduce'; -import { filterFieldsByNameTransformer } from './transformers/filterByName'; -import { transformDataFrame } from './transformDataFrame'; import { FieldType } from '../types'; +import { mockTransformationsRegistry } from '../utils/tests/mockTransformationsRegistry'; + +import { ReducerID } from './fieldReducer'; +import { transformDataFrame } from './transformDataFrame'; +import { filterFieldsByNameTransformer } from './transformers/filterByName'; +import { DataTransformerID } from './transformers/ids'; +import { reduceTransformer } from './transformers/reduce'; const seriesAWithSingleField = toDataFrame({ name: 'A', diff --git a/packages/grafana-data/src/transformations/transformDataFrame.ts b/packages/grafana-data/src/transformations/transformDataFrame.ts index 7f46fee8e50..14d3800e176 100644 --- a/packages/grafana-data/src/transformations/transformDataFrame.ts +++ b/packages/grafana-data/src/transformations/transformDataFrame.ts @@ -2,6 +2,7 @@ import { MonoTypeOperatorFunction, Observable, of } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; import { DataFrame, DataTransformerConfig } from '../types'; + import { standardTransformersRegistry, TransformerRegistryItem } from './standardTransformersRegistry'; const getOperator = diff --git a/packages/grafana-data/src/transformations/transformers.ts b/packages/grafana-data/src/transformations/transformers.ts index 063e9f8ddf4..3836a6929da 100644 --- a/packages/grafana-data/src/transformations/transformers.ts +++ b/packages/grafana-data/src/transformations/transformers.ts @@ -1,25 +1,25 @@ -import { reduceTransformer } from './transformers/reduce'; -import { concatenateTransformer } from './transformers/concat'; import { calculateFieldTransformer } from './transformers/calculateField'; +import { concatenateTransformer } from './transformers/concat'; +import { convertFieldTypeTransformer } from './transformers/convertFieldType'; +import { ensureColumnsTransformer } from './transformers/ensureColumns'; import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter'; import { filterFieldsByNameTransformer } from './transformers/filterByName'; -import { noopTransformer } from './transformers/noop'; import { filterFramesByRefIdTransformer } from './transformers/filterByRefId'; +import { filterByValueTransformer } from './transformers/filterByValue'; +import { groupByTransformer } from './transformers/groupBy'; +import { groupingToMatrixTransformer } from './transformers/groupingToMatrix'; +import { histogramTransformer } from './transformers/histogram'; +import { labelsToFieldsTransformer } from './transformers/labelsToFields'; +import { mergeTransformer } from './transformers/merge'; +import { noopTransformer } from './transformers/noop'; import { orderFieldsTransformer } from './transformers/order'; import { organizeFieldsTransformer } from './transformers/organize'; +import { reduceTransformer } from './transformers/reduce'; +import { renameFieldsTransformer } from './transformers/rename'; +import { renameByRegexTransformer } from './transformers/renameByRegex'; import { seriesToColumnsTransformer } from './transformers/seriesToColumns'; import { seriesToRowsTransformer } from './transformers/seriesToRows'; -import { renameFieldsTransformer } from './transformers/rename'; -import { labelsToFieldsTransformer } from './transformers/labelsToFields'; -import { ensureColumnsTransformer } from './transformers/ensureColumns'; -import { groupByTransformer } from './transformers/groupBy'; import { sortByTransformer } from './transformers/sortBy'; -import { mergeTransformer } from './transformers/merge'; -import { renameByRegexTransformer } from './transformers/renameByRegex'; -import { filterByValueTransformer } from './transformers/filterByValue'; -import { histogramTransformer } from './transformers/histogram'; -import { convertFieldTypeTransformer } from './transformers/convertFieldType'; -import { groupingToMatrixTransformer } from './transformers/groupingToMatrix'; export const standardTransformers = { noopTransformer, diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts index 5f0a6be0cd5..0ebf4030b33 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts @@ -1,13 +1,14 @@ -import { DataTransformerID } from './ids'; -import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldType } from '../../types/dataFrame'; -import { ReducerID } from '../fieldReducer'; -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { transformDataFrame } from '../transformDataFrame'; -import { CalculateFieldMode, calculateFieldTransformer, ReduceOptions } from './calculateField'; import { DataFrameView } from '../../dataframe'; -import { BinaryOperationID } from '../../utils'; +import { toDataFrame } from '../../dataframe/processDataFrame'; import { ScopedVars } from '../../types'; +import { FieldType } from '../../types/dataFrame'; +import { BinaryOperationID } from '../../utils'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { ReducerID } from '../fieldReducer'; +import { transformDataFrame } from '../transformDataFrame'; + +import { CalculateFieldMode, calculateFieldTransformer, ReduceOptions } from './calculateField'; +import { DataTransformerID } from './ids'; const seriesA = toDataFrame({ fields: [ diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.ts b/packages/grafana-data/src/transformations/transformers/calculateField.ts index e49920f803a..9462d56d113 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.ts @@ -1,18 +1,19 @@ +import { defaults } from 'lodash'; import { map } from 'rxjs/operators'; +import { getTimeField } from '../../dataframe/processDataFrame'; +import { getFieldDisplayName } from '../../field'; import { DataFrame, DataTransformerInfo, Field, FieldType, NullValueMode, Vector } from '../../types'; -import { DataTransformerID } from './ids'; +import { BinaryOperationID, binaryOperators } from '../../utils/binaryOperators'; +import { ArrayVector, BinaryOperationVector, ConstantVector } from '../../vector'; +import { AsNumberVector } from '../../vector/AsNumberVector'; +import { RowVector } from '../../vector/RowVector'; import { doStandardCalcs, fieldReducers, ReducerID } from '../fieldReducer'; import { getFieldMatcher } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; -import { RowVector } from '../../vector/RowVector'; -import { ArrayVector, BinaryOperationVector, ConstantVector } from '../../vector'; -import { AsNumberVector } from '../../vector/AsNumberVector'; -import { getTimeField } from '../../dataframe/processDataFrame'; -import { defaults } from 'lodash'; -import { BinaryOperationID, binaryOperators } from '../../utils/binaryOperators'; + import { ensureColumnsTransformer } from './ensureColumns'; -import { getFieldDisplayName } from '../../field'; +import { DataTransformerID } from './ids'; import { noopTransformer } from './noop'; export enum CalculateFieldMode { diff --git a/packages/grafana-data/src/transformations/transformers/concat.test.ts b/packages/grafana-data/src/transformations/transformers/concat.test.ts index f7d093289af..20d5b9ebaa9 100644 --- a/packages/grafana-data/src/transformations/transformers/concat.test.ts +++ b/packages/grafana-data/src/transformations/transformers/concat.test.ts @@ -1,4 +1,5 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; + import { concatenateFields, ConcatenateFrameNameMode } from './concat'; export const simpleABC = toDataFrame({ diff --git a/packages/grafana-data/src/transformations/transformers/concat.ts b/packages/grafana-data/src/transformations/transformers/concat.ts index e7489d3bebc..e19728f0686 100644 --- a/packages/grafana-data/src/transformations/transformers/concat.ts +++ b/packages/grafana-data/src/transformations/transformers/concat.ts @@ -1,10 +1,11 @@ import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; import { DataFrame, Field, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame'; +import { DataTransformerInfo } from '../../types/transformations'; import { ArrayVector } from '../../vector'; +import { DataTransformerID } from './ids'; + export enum ConcatenateFrameNameMode { /** * Ignore the source frame name when moving to the destination diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts index 8c74f1edbcd..467222765a4 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts @@ -2,6 +2,7 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; import { Field, FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ArrayVector } from '../../vector'; + import { ensureTimeField, convertFieldType, diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts index dbfd943df6e..0ebf969524f 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts @@ -1,13 +1,14 @@ -import { SynchronousDataTransformerInfo } from '../../types'; import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataFrame, Field, FieldType } from '../../types/dataFrame'; import { dateTimeParse } from '../../datetime'; +import { SynchronousDataTransformerInfo } from '../../types'; +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; import { ArrayVector } from '../../vector'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; +import { DataTransformerID } from './ids'; + export interface ConvertFieldTypeTransformerOptions { conversions: ConvertFieldTypeOptions[]; } diff --git a/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts b/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts index 5ed213c2fe7..75290127225 100644 --- a/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts +++ b/packages/grafana-data/src/transformations/transformers/ensureColumns.test.ts @@ -1,9 +1,10 @@ -import { DataTransformerID } from './ids'; import { toDataFrame } from '../../dataframe/processDataFrame'; import { FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; + import { ensureColumnsTransformer } from './ensureColumns'; +import { DataTransformerID } from './ids'; import { seriesToColumnsTransformer } from './seriesToColumns'; const seriesA = toDataFrame({ diff --git a/packages/grafana-data/src/transformations/transformers/ensureColumns.ts b/packages/grafana-data/src/transformations/transformers/ensureColumns.ts index 5b7f20511de..6f331e15f5f 100644 --- a/packages/grafana-data/src/transformations/transformers/ensureColumns.ts +++ b/packages/grafana-data/src/transformations/transformers/ensureColumns.ts @@ -1,10 +1,12 @@ -import { seriesToColumnsTransformer } from './seriesToColumns'; -import { DataFrame } from '../../types/dataFrame'; -import { getTimeField } from '../../dataframe/processDataFrame'; -import { SynchronousDataTransformerInfo } from '../../types/transformations'; -import { DataTransformerID } from './ids'; import { map } from 'rxjs/operators'; +import { getTimeField } from '../../dataframe/processDataFrame'; +import { DataFrame } from '../../types/dataFrame'; +import { SynchronousDataTransformerInfo } from '../../types/transformations'; + +import { DataTransformerID } from './ids'; +import { seriesToColumnsTransformer } from './seriesToColumns'; + export const ensureColumnsTransformer: SynchronousDataTransformerInfo = { id: DataTransformerID.ensureColumns, name: 'Ensure Columns Transformer', diff --git a/packages/grafana-data/src/transformations/transformers/filter.test.ts b/packages/grafana-data/src/transformations/transformers/filter.test.ts index 525acc92aa8..8959487020f 100644 --- a/packages/grafana-data/src/transformations/transformers/filter.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filter.test.ts @@ -1,11 +1,12 @@ -import { FieldType } from '../../types/dataFrame'; -import { DataTransformerID } from './ids'; import { toDataFrame } from '../../dataframe/processDataFrame'; -import { FieldMatcherID } from '../matchers/ids'; +import { FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { filterFieldsTransformer } from './filter'; +import { FieldMatcherID } from '../matchers/ids'; import { transformDataFrame } from '../transformDataFrame'; +import { filterFieldsTransformer } from './filter'; +import { DataTransformerID } from './ids'; + export const simpleSeriesWithTypes = toDataFrame({ fields: [ { name: 'A', type: FieldType.time, values: [1000, 2000] }, diff --git a/packages/grafana-data/src/transformations/transformers/filter.ts b/packages/grafana-data/src/transformations/transformers/filter.ts index 4ab327f9cd4..64a102b5514 100644 --- a/packages/grafana-data/src/transformations/transformers/filter.ts +++ b/packages/grafana-data/src/transformations/transformers/filter.ts @@ -1,11 +1,12 @@ import { map } from 'rxjs/operators'; -import { noopTransformer } from './noop'; import { DataFrame, Field } from '../../types/dataFrame'; -import { DataTransformerID } from './ids'; import { DataTransformerInfo, MatcherConfig } from '../../types/transformations'; import { getFieldMatcher, getFrameMatchers } from '../matchers'; +import { DataTransformerID } from './ids'; +import { noopTransformer } from './noop'; + export interface FilterOptions { include?: MatcherConfig; exclude?: MatcherConfig; diff --git a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts index 9d4f06c2742..fa9b5cae4b9 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts @@ -1,11 +1,12 @@ -import { DataTransformerID } from './ids'; import { toDataFrame } from '../../dataframe/processDataFrame'; import { FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { filterFieldsByNameTransformer } from './filterByName'; -import { filterFieldsTransformer } from './filter'; import { transformDataFrame } from '../transformDataFrame'; +import { filterFieldsTransformer } from './filter'; +import { filterFieldsByNameTransformer } from './filterByName'; +import { DataTransformerID } from './ids'; + export const seriesWithNamesToMatch = toDataFrame({ fields: [ { name: 'startsWithA', type: FieldType.time, values: [1000, 2000] }, diff --git a/packages/grafana-data/src/transformations/transformers/filterByName.ts b/packages/grafana-data/src/transformations/transformers/filterByName.ts index f6f58cbe60a..0f461eb81f9 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByName.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByName.ts @@ -1,9 +1,10 @@ -import { DataTransformerID } from './ids'; import { DataTransformerInfo, MatcherConfig } from '../../types/transformations'; import { FieldMatcherID } from '../matchers/ids'; -import { filterFieldsTransformer } from './filter'; import { RegexpOrNamesMatcherOptions } from '../matchers/nameMatcher'; +import { filterFieldsTransformer } from './filter'; +import { DataTransformerID } from './ids'; + export interface FilterFieldsByNameTransformerOptions { include?: RegexpOrNamesMatcherOptions; exclude?: RegexpOrNamesMatcherOptions; diff --git a/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts b/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts index a989e28ae11..f3c88698a6c 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts @@ -1,9 +1,10 @@ -import { DataTransformerID } from './ids'; import { toDataFrame } from '../../dataframe/processDataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { filterFramesByRefIdTransformer } from './filterByRefId'; import { transformDataFrame } from '../transformDataFrame'; +import { filterFramesByRefIdTransformer } from './filterByRefId'; +import { DataTransformerID } from './ids'; + export const allSeries = [ toDataFrame({ refId: 'A', diff --git a/packages/grafana-data/src/transformations/transformers/filterByRefId.ts b/packages/grafana-data/src/transformations/transformers/filterByRefId.ts index 25b7b09e73d..29e6f50a808 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByRefId.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByRefId.ts @@ -1,8 +1,9 @@ -import { DataTransformerID } from './ids'; -import { filterFramesTransformer, FilterOptions } from './filter'; import { DataTransformerInfo } from '../../types/transformations'; import { FrameMatcherID } from '../matchers/ids'; +import { filterFramesTransformer, FilterOptions } from './filter'; +import { DataTransformerID } from './ids'; + export interface FilterFramesByRefIdTransformerOptions { include?: string; exclude?: string; diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts index 062bb89bbc0..96fd6b4dae0 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts @@ -1,8 +1,11 @@ -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { DataTransformerConfig, FieldType, MatcherConfig } from '../../types'; -import { ArrayVector } from '../../vector'; -import { transformDataFrame } from '../transformDataFrame'; import { toDataFrame } from '../../dataframe/processDataFrame'; +import { DataTransformerConfig, FieldType, MatcherConfig } from '../../types'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { ArrayVector } from '../../vector'; +import { ValueMatcherID } from '../matchers/ids'; +import { BasicValueMatcherOptions } from '../matchers/valueMatchers/types'; +import { transformDataFrame } from '../transformDataFrame'; + import { FilterByValueMatch, filterByValueTransformer, @@ -10,8 +13,6 @@ import { FilterByValueType, } from './filterByValue'; import { DataTransformerID } from './ids'; -import { ValueMatcherID } from '../matchers/ids'; -import { BasicValueMatcherOptions } from '../matchers/valueMatchers/types'; const seriesAWithSingleField = toDataFrame({ name: 'A', diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.ts index 9994821cb30..d12838c9d4a 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.ts @@ -1,12 +1,13 @@ import { map } from 'rxjs/operators'; -import { noopTransformer } from './noop'; -import { DataTransformerID } from './ids'; -import { DataTransformerInfo, MatcherConfig } from '../../types/transformations'; -import { DataFrame, Field } from '../../types/dataFrame'; import { getFieldDisplayName } from '../../field/fieldState'; -import { getValueMatcher } from '../matchers'; +import { DataFrame, Field } from '../../types/dataFrame'; +import { DataTransformerInfo, MatcherConfig } from '../../types/transformations'; import { ArrayVector } from '../../vector/ArrayVector'; +import { getValueMatcher } from '../matchers'; + +import { DataTransformerID } from './ids'; +import { noopTransformer } from './noop'; export enum FilterByValueType { exclude = 'exclude', diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts index 76e420f61f6..d9e0e92cfeb 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.test.ts @@ -1,12 +1,14 @@ +import { DataTransformerConfig } from '@grafana/data'; + import { toDataFrame } from '../../dataframe/processDataFrame'; -import { GroupByOperationID, groupByTransformer, GroupByTransformerOptions } from './groupBy'; -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { transformDataFrame } from '../transformDataFrame'; import { Field, FieldType } from '../../types'; -import { DataTransformerID } from './ids'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ArrayVector } from '../../vector'; import { ReducerID } from '../fieldReducer'; -import { DataTransformerConfig } from '@grafana/data'; +import { transformDataFrame } from '../transformDataFrame'; + +import { GroupByOperationID, groupByTransformer, GroupByTransformerOptions } from './groupBy'; +import { DataTransformerID } from './ids'; describe('GroupBy transformer', () => { beforeAll(() => { diff --git a/packages/grafana-data/src/transformations/transformers/groupBy.ts b/packages/grafana-data/src/transformations/transformers/groupBy.ts index be1763833d2..1c95825d529 100644 --- a/packages/grafana-data/src/transformations/transformers/groupBy.ts +++ b/packages/grafana-data/src/transformations/transformers/groupBy.ts @@ -1,13 +1,14 @@ import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; +import { MutableField } from '../../dataframe/MutableDataFrame'; +import { guessFieldTypeForField } from '../../dataframe/processDataFrame'; +import { getFieldDisplayName } from '../../field/fieldState'; import { DataFrame, Field, FieldType } from '../../types/dataFrame'; import { DataTransformerInfo } from '../../types/transformations'; -import { getFieldDisplayName } from '../../field/fieldState'; import { ArrayVector } from '../../vector/ArrayVector'; -import { guessFieldTypeForField } from '../../dataframe/processDataFrame'; import { reduceField, ReducerID } from '../fieldReducer'; -import { MutableField } from '../../dataframe/MutableDataFrame'; + +import { DataTransformerID } from './ids'; export enum GroupByOperationID { aggregate = 'aggregate', diff --git a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts index 103cf335a67..aea04e8ce70 100644 --- a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts +++ b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.test.ts @@ -7,9 +7,11 @@ import { toDataFrame, transformDataFrame, } from '@grafana/data'; -import { GroupingToMatrixTransformerOptions, groupingToMatrixTransformer } from './groupingToMatrix'; + import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { GroupingToMatrixTransformerOptions, groupingToMatrixTransformer } from './groupingToMatrix'; + describe('Grouping to Matrix', () => { beforeAll(() => { mockTransformationsRegistry([groupingToMatrixTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts index 4d8a8729ff7..b73b6cf0520 100644 --- a/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts +++ b/packages/grafana-data/src/transformations/transformers/groupingToMatrix.ts @@ -1,9 +1,10 @@ import { map } from 'rxjs/operators'; -import { DataFrame, DataTransformerInfo, Field, FieldType, Vector } from '../../types'; -import { DataTransformerID } from './ids'; import { MutableDataFrame } from '../../dataframe'; import { getFieldDisplayName } from '../../field/fieldState'; +import { DataFrame, DataTransformerInfo, Field, FieldType, Vector } from '../../types'; + +import { DataTransformerID } from './ids'; export interface GroupingToMatrixTransformerOptions { columnField?: string; diff --git a/packages/grafana-data/src/transformations/transformers/histogram.test.ts b/packages/grafana-data/src/transformations/transformers/histogram.test.ts index 22e9ea0f06a..a2f0b1952a2 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.test.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.test.ts @@ -1,6 +1,7 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; import { FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; + import { histogramTransformer, buildHistogram, histogramFieldsToFrame } from './histogram'; describe('histogram frames frames', () => { diff --git a/packages/grafana-data/src/transformations/transformers/histogram.ts b/packages/grafana-data/src/transformations/transformers/histogram.ts index a48f00c1861..9dc72bcfd31 100644 --- a/packages/grafana-data/src/transformations/transformers/histogram.ts +++ b/packages/grafana-data/src/transformations/transformers/histogram.ts @@ -1,12 +1,13 @@ -import { SynchronousDataTransformerInfo } from '../../types'; import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataFrame, Field, FieldConfig, FieldType } from '../../types/dataFrame'; -import { ArrayVector } from '../../vector/ArrayVector'; -import { AlignedData, join } from './joinDataFrames'; import { getDisplayProcessor } from '../../field'; import { createTheme, GrafanaTheme2 } from '../../themes'; +import { SynchronousDataTransformerInfo } from '../../types'; +import { DataFrame, Field, FieldConfig, FieldType } from '../../types/dataFrame'; +import { ArrayVector } from '../../vector/ArrayVector'; + +import { DataTransformerID } from './ids'; +import { AlignedData, join } from './joinDataFrames'; /** * @internal diff --git a/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts b/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts index d1612fdc839..2f78fa5c901 100644 --- a/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts +++ b/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts @@ -2,6 +2,7 @@ import { toDataFrame } from '../../dataframe/processDataFrame'; import { FieldType } from '../../types/dataFrame'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ArrayVector } from '../../vector'; + import { calculateFieldTransformer } from './calculateField'; import { isLikelyAscendingVector, outerJoinDataFrames } from './joinDataFrames'; @@ -188,7 +189,7 @@ describe('align frames', () => { ], }); - const out = outerJoinDataFrames({ frames: [series1], enforceSort: true, keepOriginIndices: true })!; + const out = outerJoinDataFrames({ frames: [series1], keepOriginIndices: true })!; expect( out.fields.map((f) => ({ name: f.name, diff --git a/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts b/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts index 4288f3690e4..39b95b6ae3c 100644 --- a/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts +++ b/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts @@ -1,8 +1,8 @@ +import { getTimeField, sortDataFrame } from '../../dataframe'; import { DataFrame, Field, FieldMatcher, FieldType, Vector } from '../../types'; import { ArrayVector } from '../../vector'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; -import { getTimeField, sortDataFrame } from '../../dataframe'; export function pickBestJoinField(data: DataFrame[]): FieldMatcher { const { timeField } = getTimeField(data[0]); @@ -48,13 +48,6 @@ export interface JoinOptions { */ keep?: FieldMatcher; - /** - * When the result is a single frame, this will to a quick check to see if the values are sorted, - * and sort if necessary. If the first/last values are in order the whole vector is assumed to be - * sorted - */ - enforceSort?: boolean; - /** * @internal -- used when we need to keep a reference to the original frame/field index */ @@ -65,6 +58,21 @@ function getJoinMatcher(options: JoinOptions): FieldMatcher { return options.joinBy ?? pickBestJoinField(options.frames); } +/** + * @internal + */ +export function maybeSortFrame(frame: DataFrame, fieldIdx: number) { + if (fieldIdx >= 0) { + let sortByField = frame.fields[fieldIdx]; + + if (sortByField.type !== FieldType.string && !isLikelyAscendingVector(sortByField.values)) { + frame = sortDataFrame(frame, fieldIdx); + } + } + + return frame; +} + /** * This will return a single frame joined by the first matching field. When a join field is not specified, * the default will use the first time field @@ -109,12 +117,8 @@ export function outerJoinDataFrames(options: JoinOptions): DataFrame | undefined } } - if (options.enforceSort) { - if (joinIndex >= 0) { - if (!isLikelyAscendingVector(frameCopy.fields[joinIndex].values)) { - frameCopy = sortDataFrame(frameCopy, joinIndex); - } - } + if (joinIndex >= 0) { + frameCopy = maybeSortFrame(frameCopy, joinIndex); } if (options.keep) { diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts index 515b39c42ae..41dccaeb826 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts @@ -1,10 +1,11 @@ -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { LabelsToFieldsMode, LabelsToFieldsOptions, labelsToFieldsTransformer } from './labelsToFields'; -import { DataFrame, DataTransformerConfig, FieldDTO, FieldType } from '../../types'; -import { DataTransformerID } from './ids'; import { toDataFrame, toDataFrameDTO } from '../../dataframe'; +import { DataFrame, DataTransformerConfig, FieldDTO, FieldType } from '../../types'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; +import { DataTransformerID } from './ids'; +import { LabelsToFieldsMode, LabelsToFieldsOptions, labelsToFieldsTransformer } from './labelsToFields'; + describe('Labels as Columns', () => { beforeAll(() => { mockTransformationsRegistry([labelsToFieldsTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts index f60e68cb86c..c4814f7ecc7 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts @@ -1,9 +1,10 @@ import { map } from 'rxjs/operators'; -import { DataFrame, Field, FieldType, SynchronousDataTransformerInfo } from '../../types'; -import { DataTransformerID } from './ids'; -import { ArrayVector } from '../../vector'; import { getFieldDisplayName } from '../..'; +import { DataFrame, Field, FieldType, SynchronousDataTransformerInfo } from '../../types'; +import { ArrayVector } from '../../vector'; + +import { DataTransformerID } from './ids'; export enum LabelsToFieldsMode { Columns = 'columns', // default mode diff --git a/packages/grafana-data/src/transformations/transformers/merge.test.ts b/packages/grafana-data/src/transformations/transformers/merge.test.ts index 8fb57860490..9c4b210e42f 100644 --- a/packages/grafana-data/src/transformations/transformers/merge.test.ts +++ b/packages/grafana-data/src/transformations/transformers/merge.test.ts @@ -1,9 +1,10 @@ -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { DataTransformerConfig, DisplayProcessor, Field, FieldType } from '../../types'; -import { DataTransformerID } from './ids'; import { toDataFrame } from '../../dataframe'; -import { transformDataFrame } from '../transformDataFrame'; +import { DataTransformerConfig, DisplayProcessor, Field, FieldType } from '../../types'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ArrayVector } from '../../vector'; +import { transformDataFrame } from '../transformDataFrame'; + +import { DataTransformerID } from './ids'; import { mergeTransformer, MergeTransformerOptions } from './merge'; describe('Merge multiple to single', () => { diff --git a/packages/grafana-data/src/transformations/transformers/merge.ts b/packages/grafana-data/src/transformations/transformers/merge.ts index cecbb1cf9c7..55185c0756d 100644 --- a/packages/grafana-data/src/transformations/transformers/merge.ts +++ b/packages/grafana-data/src/transformations/transformers/merge.ts @@ -1,11 +1,12 @@ +import { omit } from 'lodash'; import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; -import { DataFrame, Field } from '../../types/dataFrame'; -import { omit } from 'lodash'; -import { ArrayVector } from '../../vector/ArrayVector'; import { MutableDataFrame } from '../../dataframe'; +import { DataFrame, Field } from '../../types/dataFrame'; +import { DataTransformerInfo } from '../../types/transformations'; +import { ArrayVector } from '../../vector/ArrayVector'; + +import { DataTransformerID } from './ids'; interface ValuePointer { key: string; diff --git a/packages/grafana-data/src/transformations/transformers/noop.ts b/packages/grafana-data/src/transformations/transformers/noop.ts index 32c60101086..ba70bd27b36 100644 --- a/packages/grafana-data/src/transformations/transformers/noop.ts +++ b/packages/grafana-data/src/transformations/transformers/noop.ts @@ -1,6 +1,7 @@ -import { DataTransformerID } from './ids'; import { DataTransformerInfo } from '../../types/transformations'; +import { DataTransformerID } from './ids'; + export interface NoopTransformerOptions { include?: string; exclude?: string; diff --git a/packages/grafana-data/src/transformations/transformers/order.test.ts b/packages/grafana-data/src/transformations/transformers/order.test.ts index 8ecd42ec4e1..41c74ad0433 100644 --- a/packages/grafana-data/src/transformations/transformers/order.test.ts +++ b/packages/grafana-data/src/transformations/transformers/order.test.ts @@ -6,9 +6,11 @@ import { toDataFrame, transformDataFrame, } from '@grafana/data'; -import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order'; + import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order'; + describe('Order Transformer', () => { beforeAll(() => { mockTransformationsRegistry([orderFieldsTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/order.ts b/packages/grafana-data/src/transformations/transformers/order.ts index 577d154da8a..402f90058d0 100644 --- a/packages/grafana-data/src/transformations/transformers/order.ts +++ b/packages/grafana-data/src/transformations/transformers/order.ts @@ -1,9 +1,11 @@ -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; -import { DataFrame, Field } from '../../types'; -import { getFieldDisplayName } from '../../field/fieldState'; import { map } from 'rxjs/operators'; +import { getFieldDisplayName } from '../../field/fieldState'; +import { DataFrame, Field } from '../../types'; +import { DataTransformerInfo } from '../../types/transformations'; + +import { DataTransformerID } from './ids'; + export interface OrderFieldsTransformerOptions { indexByName: Record; } diff --git a/packages/grafana-data/src/transformations/transformers/organize.test.ts b/packages/grafana-data/src/transformations/transformers/organize.test.ts index 28ec939938a..4388237853d 100644 --- a/packages/grafana-data/src/transformations/transformers/organize.test.ts +++ b/packages/grafana-data/src/transformations/transformers/organize.test.ts @@ -6,9 +6,11 @@ import { toDataFrame, transformDataFrame, } from '@grafana/data'; -import { organizeFieldsTransformer, OrganizeFieldsTransformerOptions } from './organize'; + import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { organizeFieldsTransformer, OrganizeFieldsTransformerOptions } from './organize'; + describe('OrganizeFields Transformer', () => { beforeAll(() => { mockTransformationsRegistry([organizeFieldsTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/organize.ts b/packages/grafana-data/src/transformations/transformers/organize.ts index 7913cdb87bb..3a13a0fbc47 100644 --- a/packages/grafana-data/src/transformations/transformers/organize.ts +++ b/packages/grafana-data/src/transformations/transformers/organize.ts @@ -1,7 +1,8 @@ -import { DataTransformerID } from './ids'; import { DataTransformerInfo } from '../../types/transformations'; -import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order'; + import { filterFieldsByNameTransformer } from './filterByName'; +import { DataTransformerID } from './ids'; +import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order'; import { renameFieldsTransformer, RenameFieldsTransformerOptions } from './rename'; export interface OrganizeFieldsTransformerOptions diff --git a/packages/grafana-data/src/transformations/transformers/reduce.test.ts b/packages/grafana-data/src/transformations/transformers/reduce.test.ts index 721d34514a5..92369e07222 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.test.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.test.ts @@ -1,13 +1,14 @@ -import { ReducerID } from '../fieldReducer'; -import { DataTransformerID } from './ids'; -import { toDataFrame } from '../../dataframe/processDataFrame'; -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { reduceFields, reduceTransformer, ReduceTransformerOptions } from './reduce'; -import { transformDataFrame } from '../transformDataFrame'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; -import { ArrayVector } from '../../vector'; -import { notTimeFieldMatcher } from '../matchers/predicates'; import { DataFrameView } from '../../dataframe'; +import { toDataFrame } from '../../dataframe/processDataFrame'; +import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { ArrayVector } from '../../vector'; +import { ReducerID } from '../fieldReducer'; +import { notTimeFieldMatcher } from '../matchers/predicates'; +import { transformDataFrame } from '../transformDataFrame'; + +import { DataTransformerID } from './ids'; +import { reduceFields, reduceTransformer, ReduceTransformerOptions } from './reduce'; const seriesAWithSingleField = toDataFrame({ name: 'A', diff --git a/packages/grafana-data/src/transformations/transformers/reduce.ts b/packages/grafana-data/src/transformations/transformers/reduce.ts index 1250dc9039d..7a5beed909f 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.ts @@ -1,15 +1,16 @@ import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataTransformerInfo, FieldMatcher, MatcherConfig } from '../../types/transformations'; -import { fieldReducers, reduceField, ReducerID } from '../fieldReducer'; -import { alwaysFieldMatcher, notTimeFieldMatcher } from '../matchers/predicates'; -import { DataFrame, Field, FieldType } from '../../types/dataFrame'; -import { ArrayVector } from '../../vector/ArrayVector'; -import { KeyValue } from '../../types/data'; import { guessFieldTypeForField } from '../../dataframe/processDataFrame'; -import { getFieldMatcher } from '../matchers'; import { getFieldDisplayName } from '../../field'; +import { KeyValue } from '../../types/data'; +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; +import { DataTransformerInfo, FieldMatcher, MatcherConfig } from '../../types/transformations'; +import { ArrayVector } from '../../vector/ArrayVector'; +import { fieldReducers, reduceField, ReducerID } from '../fieldReducer'; +import { getFieldMatcher } from '../matchers'; +import { alwaysFieldMatcher, notTimeFieldMatcher } from '../matchers/predicates'; + +import { DataTransformerID } from './ids'; export enum ReduceTransformerMode { SeriesToRows = 'seriesToRows', // default diff --git a/packages/grafana-data/src/transformations/transformers/rename.test.ts b/packages/grafana-data/src/transformations/transformers/rename.test.ts index 10cff84ab99..db64088de31 100644 --- a/packages/grafana-data/src/transformations/transformers/rename.test.ts +++ b/packages/grafana-data/src/transformations/transformers/rename.test.ts @@ -6,9 +6,11 @@ import { toDataFrame, transformDataFrame, } from '@grafana/data'; -import { renameFieldsTransformer, RenameFieldsTransformerOptions } from './rename'; + import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { renameFieldsTransformer, RenameFieldsTransformerOptions } from './rename'; + describe('Rename Transformer', () => { beforeAll(() => { mockTransformationsRegistry([renameFieldsTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/rename.ts b/packages/grafana-data/src/transformations/transformers/rename.ts index 3ecb9170c9d..a19f40e0359 100644 --- a/packages/grafana-data/src/transformations/transformers/rename.ts +++ b/packages/grafana-data/src/transformations/transformers/rename.ts @@ -1,9 +1,11 @@ -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; -import { DataFrame, Field } from '../../types/dataFrame'; -import { getFieldDisplayName } from '../../field/fieldState'; import { map } from 'rxjs/operators'; +import { getFieldDisplayName } from '../../field/fieldState'; +import { DataFrame, Field } from '../../types/dataFrame'; +import { DataTransformerInfo } from '../../types/transformations'; + +import { DataTransformerID } from './ids'; + export interface RenameFieldsTransformerOptions { renameByName: Record; } diff --git a/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts b/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts index ae617320ab9..aa630829a58 100644 --- a/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts +++ b/packages/grafana-data/src/transformations/transformers/renameByRegex.test.ts @@ -1,7 +1,9 @@ import { DataTransformerConfig, DataTransformerID, FieldType, toDataFrame, transformDataFrame } from '@grafana/data'; -import { renameByRegexTransformer, RenameByRegexTransformerOptions } from './renameByRegex'; + import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { renameByRegexTransformer, RenameByRegexTransformerOptions } from './renameByRegex'; + describe('Rename By Regex Transformer', () => { beforeAll(() => { mockTransformationsRegistry([renameByRegexTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/renameByRegex.ts b/packages/grafana-data/src/transformations/transformers/renameByRegex.ts index be93f52a4ec..05426c988c0 100644 --- a/packages/grafana-data/src/transformations/transformers/renameByRegex.ts +++ b/packages/grafana-data/src/transformations/transformers/renameByRegex.ts @@ -1,8 +1,10 @@ -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; import { map } from 'rxjs/operators'; -import { DataFrame } from '../../types/dataFrame'; + import { getFieldDisplayName } from '../../field/fieldState'; +import { DataFrame } from '../../types/dataFrame'; +import { DataTransformerInfo } from '../../types/transformations'; + +import { DataTransformerID } from './ids'; /** * Options for renameByRegexTransformer diff --git a/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts b/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts index 24cd021ea8c..6ce2f840424 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToColumns.test.ts @@ -6,9 +6,11 @@ import { toDataFrame, transformDataFrame, } from '@grafana/data'; -import { SeriesToColumnsOptions, seriesToColumnsTransformer } from './seriesToColumns'; + import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { SeriesToColumnsOptions, seriesToColumnsTransformer } from './seriesToColumns'; + describe('SeriesToColumns Transformer', () => { beforeAll(() => { mockTransformationsRegistry([seriesToColumnsTransformer]); diff --git a/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts b/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts index 08170d1192b..785a8be6fda 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts @@ -1,11 +1,12 @@ import { map } from 'rxjs/operators'; import { DataFrame, SynchronousDataTransformerInfo, FieldMatcher } from '../../types'; -import { DataTransformerID } from './ids'; -import { outerJoinDataFrames } from './joinDataFrames'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; +import { DataTransformerID } from './ids'; +import { outerJoinDataFrames } from './joinDataFrames'; + export interface SeriesToColumnsOptions { byField?: string; // empty will pick the field automatically } diff --git a/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts b/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts index bb43826010f..9cfc1d930ef 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToRows.test.ts @@ -1,9 +1,10 @@ -import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; -import { DataTransformerID } from './ids'; import { toDataFrame } from '../../dataframe'; -import { transformDataFrame } from '../transformDataFrame'; +import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { ArrayVector } from '../../vector'; +import { transformDataFrame } from '../transformDataFrame'; + +import { DataTransformerID } from './ids'; import { seriesToRowsTransformer, SeriesToRowsTransformerOptions } from './seriesToRows'; describe('Series to rows', () => { diff --git a/packages/grafana-data/src/transformations/transformers/seriesToRows.ts b/packages/grafana-data/src/transformations/transformers/seriesToRows.ts index a2ddb11f80e..8121784b476 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToRows.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToRows.ts @@ -1,8 +1,9 @@ import { omit } from 'lodash'; import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; +import { MutableDataFrame, sortDataFrame } from '../../dataframe'; +import { isTimeSeries } from '../../dataframe/utils'; +import { getFrameDisplayName } from '../../field/fieldState'; import { Field, FieldType, @@ -10,10 +11,10 @@ import { TIME_SERIES_TIME_FIELD_NAME, TIME_SERIES_VALUE_FIELD_NAME, } from '../../types/dataFrame'; -import { isTimeSeries } from '../../dataframe/utils'; -import { MutableDataFrame, sortDataFrame } from '../../dataframe'; +import { DataTransformerInfo } from '../../types/transformations'; import { ArrayVector } from '../../vector'; -import { getFrameDisplayName } from '../../field/fieldState'; + +import { DataTransformerID } from './ids'; export interface SeriesToRowsTransformerOptions {} diff --git a/packages/grafana-data/src/transformations/transformers/sortBy.test.ts b/packages/grafana-data/src/transformations/transformers/sortBy.test.ts index 534bd5680ec..6a12bbde0f6 100644 --- a/packages/grafana-data/src/transformations/transformers/sortBy.test.ts +++ b/packages/grafana-data/src/transformations/transformers/sortBy.test.ts @@ -1,10 +1,12 @@ +import { DataTransformerConfig } from '@grafana/data'; + import { toDataFrame } from '../../dataframe/processDataFrame'; -import { sortByTransformer, SortByTransformerOptions } from './sortBy'; +import { Field, FieldType } from '../../types'; import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { transformDataFrame } from '../transformDataFrame'; -import { Field, FieldType } from '../../types'; + import { DataTransformerID } from './ids'; -import { DataTransformerConfig } from '@grafana/data'; +import { sortByTransformer, SortByTransformerOptions } from './sortBy'; const testFrame = toDataFrame({ name: 'A', diff --git a/packages/grafana-data/src/transformations/transformers/sortBy.ts b/packages/grafana-data/src/transformations/transformers/sortBy.ts index 2e377291c3f..1bf8ba2d859 100644 --- a/packages/grafana-data/src/transformations/transformers/sortBy.ts +++ b/packages/grafana-data/src/transformations/transformers/sortBy.ts @@ -1,10 +1,11 @@ import { map } from 'rxjs/operators'; -import { DataTransformerID } from './ids'; -import { DataTransformerInfo } from '../../types/transformations'; -import { DataFrame } from '../../types'; -import { getFieldDisplayName } from '../../field'; import { sortDataFrame } from '../../dataframe'; +import { getFieldDisplayName } from '../../field'; +import { DataFrame } from '../../types'; +import { DataTransformerInfo } from '../../types/transformations'; + +import { DataTransformerID } from './ids'; export interface SortByField { field: string; diff --git a/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts b/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts index 42235c7eb0a..72bc00abfac 100644 --- a/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts +++ b/packages/grafana-data/src/types/OptionsUIRegistryBuilder.ts @@ -1,11 +1,13 @@ import { ComponentType } from 'react'; -import { RegistryItem, Registry } from '../utils/Registry'; + import { NumberFieldConfigSettings, SliderFieldConfigSettings, SelectFieldConfigSettings, StringFieldConfigSettings, } from '../field'; +import { RegistryItem, Registry } from '../utils/Registry'; + import { OptionEditorConfig } from './options'; /** diff --git a/packages/grafana-data/src/types/accesscontrol.ts b/packages/grafana-data/src/types/accesscontrol.ts index 75fc32d69ca..22c2be37250 100644 --- a/packages/grafana-data/src/types/accesscontrol.ts +++ b/packages/grafana-data/src/types/accesscontrol.ts @@ -1,7 +1,7 @@ import { KeyValue } from '.'; /** - * With FGAC, the backend will return additional access control metadata to objects. + * With RBAC, the backend will return additional access control metadata to objects. * These metadata will contain user permissions associated to a given resource. * * For example: diff --git a/packages/grafana-data/src/types/annotations.ts b/packages/grafana-data/src/types/annotations.ts index 26867af8b2b..e701b6b3841 100644 --- a/packages/grafana-data/src/types/annotations.ts +++ b/packages/grafana-data/src/types/annotations.ts @@ -1,8 +1,8 @@ -import { Observable } from 'rxjs'; import { ComponentType } from 'react'; +import { Observable } from 'rxjs'; -import { QueryEditorProps } from './datasource'; import { DataFrame } from './dataFrame'; +import { QueryEditorProps } from './datasource'; import { DataQuery, DataSourceRef } from './query'; /** diff --git a/packages/grafana-data/src/types/app.ts b/packages/grafana-data/src/types/app.ts index fe23e8fc6f1..04beaa52ed0 100644 --- a/packages/grafana-data/src/types/app.ts +++ b/packages/grafana-data/src/types/app.ts @@ -1,4 +1,5 @@ import { ComponentType } from 'react'; + import { KeyValue } from './data'; import { NavModel } from './navModel'; import { PluginMeta, GrafanaPlugin, PluginIncludeType } from './plugin'; diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 8231bb4c252..bc7c0f13a15 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -1,10 +1,12 @@ +import { SystemDateFormatSettings } from '../datetime'; +import { MapLayerOptions } from '../geo/layer'; +import { GrafanaTheme2 } from '../themes'; + import { DataSourceInstanceSettings } from './datasource'; +import { FeatureToggles } from './featureToggles.gen'; import { PanelPluginMeta } from './panel'; import { GrafanaTheme } from './theme'; -import { SystemDateFormatSettings } from '../datetime'; -import { GrafanaTheme2 } from '../themes'; -import { MapLayerOptions } from '../geo/layer'; -import { FeatureToggles } from './featureToggles.gen'; + import { NavLinkDTO, OrgRole } from '.'; /** @@ -42,6 +44,7 @@ export interface LicenseInfo { stateInfo: string; edition: GrafanaEdition; enabledFeatures: { [key: string]: boolean }; + trialExpiry?: number; } /** diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts index 642c3cfee5e..ab18139742b 100644 --- a/packages/grafana-data/src/types/data.ts +++ b/packages/grafana-data/src/types/data.ts @@ -1,9 +1,10 @@ import { DataFrameDTO, FieldConfig } from './dataFrame'; -import { DataTransformerConfig } from './transformations'; -import { ApplyFieldOverrideOptions } from './fieldOverrides'; -import { PanelPluginDataSupport } from '.'; -import { DataTopic } from './query'; import { DataFrameType } from './dataFrameTypes'; +import { ApplyFieldOverrideOptions } from './fieldOverrides'; +import { DataTopic } from './query'; +import { DataTransformerConfig } from './transformations'; + +import { PanelPluginDataSupport } from '.'; export type KeyValue = Record; @@ -47,6 +48,9 @@ export interface QueryResultMeta { /** The path for live stream updates for this frame */ channel?: string; + /** Did the query response come from the cache */ + isCachedResponse?: boolean; + /** * Optionally identify which topic the frame should be assigned to. * A value specified in the response will override what the request asked for. diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index d0f6726017d..e385ca4fb15 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -1,11 +1,11 @@ +import { ScopedVars } from './ScopedVars'; +import { QueryResultBase, Labels, NullValueMode } from './data'; +import { DataLink, LinkModel } from './dataLink'; +import { DisplayProcessor, DisplayValue } from './displayValue'; +import { FieldColor } from './fieldColor'; import { ThresholdsConfig } from './thresholds'; import { ValueMapping } from './valueMapping'; -import { QueryResultBase, Labels, NullValueMode } from './data'; -import { DisplayProcessor, DisplayValue } from './displayValue'; -import { DataLink, LinkModel } from './dataLink'; import { Vector } from './vector'; -import { FieldColor } from './fieldColor'; -import { ScopedVars } from './ScopedVars'; /** @public */ export enum FieldType { diff --git a/packages/grafana-data/src/types/dataLink.ts b/packages/grafana-data/src/types/dataLink.ts index a0c6ac312e2..6d741b2d801 100644 --- a/packages/grafana-data/src/types/dataLink.ts +++ b/packages/grafana-data/src/types/dataLink.ts @@ -1,6 +1,6 @@ -import { DataQuery } from './query'; -import { InterpolateFunction } from './panel'; import { ExplorePanelsState } from './explore'; +import { InterpolateFunction } from './panel'; +import { DataQuery } from './query'; /** * Callback info for DataLink click events diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 1396140c41e..c3bf11b2ef2 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -1,17 +1,20 @@ -import { Observable } from 'rxjs'; import { ComponentType } from 'react'; -import { GrafanaPlugin, PluginMeta } from './plugin'; -import { PanelData } from './panel'; -import { LogRowModel } from './logs'; +import { Observable } from 'rxjs'; + +import { makeClassES5Compatible } from '../utils/makeClassES5Compatible'; + +import { ScopedVars } from './ScopedVars'; import { AnnotationEvent, AnnotationQuery, AnnotationSupport } from './annotations'; +import { CoreApp } from './app'; import { KeyValue, LoadingState, TableData, TimeSeries } from './data'; import { DataFrame, DataFrameDTO } from './dataFrame'; -import { RawTimeRange, TimeRange } from './time'; -import { ScopedVars } from './ScopedVars'; -import { CoreApp } from './app'; -import { CustomVariableSupport, DataSourceVariableSupport, StandardVariableSupport } from './variables'; -import { makeClassES5Compatible } from '../utils/makeClassES5Compatible'; +import { LogRowModel } from './logs'; +import { PanelData } from './panel'; +import { GrafanaPlugin, PluginMeta } from './plugin'; import { DataQuery } from './query'; +import { RawTimeRange, TimeRange } from './time'; +import { CustomVariableSupport, DataSourceVariableSupport, StandardVariableSupport } from './variables'; + import { DataSourceRef, WithAccessControlMetadata } from '.'; export interface DataSourcePluginOptionsEditorProps { diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 23e997e4dd6..080394dd1a9 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -19,10 +19,9 @@ export interface FeatureToggles { trimDefaults?: boolean; envelopeEncryption?: boolean; httpclientprovider_azure_auth?: boolean; - ['service-accounts']?: boolean; + serviceAccounts?: boolean; database_metrics?: boolean; dashboardPreviews?: boolean; - dashboardPreviewsScheduler?: boolean; dashboardPreviewsAdmin?: boolean; ['live-config']?: boolean; ['live-pipeline']?: boolean; @@ -54,4 +53,6 @@ export interface FeatureToggles { storageLocalUpload?: boolean; azureMonitorResourcePickerForMetrics?: boolean; explore2Dashboard?: boolean; + persistNotifications?: boolean; + commandPalette?: boolean; } diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 57532652ea2..088b0847a97 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -1,10 +1,12 @@ import { ComponentType } from 'react'; -import { MatcherConfig, FieldConfig, Field, DataFrame, TimeZone } from '../types'; -import { InterpolateFunction } from './panel'; + import { StandardEditorProps, FieldConfigOptionsRegistry, StandardEditorContext } from '../field'; +import { GrafanaTheme2 } from '../themes'; +import { MatcherConfig, FieldConfig, Field, DataFrame, TimeZone } from '../types'; + import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; import { OptionEditorConfig } from './options'; -import { GrafanaTheme2 } from '../themes'; +import { InterpolateFunction } from './panel'; export interface DynamicConfigValue { id: string; diff --git a/packages/grafana-data/src/types/graph.ts b/packages/grafana-data/src/types/graph.ts index 91604f5ae29..83f18ab5ab4 100644 --- a/packages/grafana-data/src/types/graph.ts +++ b/packages/grafana-data/src/types/graph.ts @@ -1,5 +1,5 @@ -import { DisplayValue } from './displayValue'; import { Field } from './dataFrame'; +import { DisplayValue } from './displayValue'; export interface YAxis { index: number; diff --git a/packages/grafana-data/src/types/legacyEvents.ts b/packages/grafana-data/src/types/legacyEvents.ts index d7e09095a1f..8154a322159 100644 --- a/packages/grafana-data/src/types/legacyEvents.ts +++ b/packages/grafana-data/src/types/legacyEvents.ts @@ -1,12 +1,13 @@ -import { DataQueryError, DataQueryResponseData } from './datasource'; -import { AngularPanelMenuItem } from './panel'; -import { DataFrame } from './dataFrame'; +import { DataHoverPayload } from '../events'; import { eventFactory } from '../events/eventFactory'; import { BusEventBase, BusEventWithPayload } from '../events/types'; -import { DataHoverPayload } from '../events'; -export type AlertPayload = [string, string?]; -export type AlertErrorPayload = [string, (string | Error)?]; +import { DataFrame } from './dataFrame'; +import { DataQueryError, DataQueryResponseData } from './datasource'; +import { AngularPanelMenuItem } from './panel'; + +export type AlertPayload = [string, string?, string?]; +export type AlertErrorPayload = [string, (string | Error)?, string?]; export const AppEvents = { alertSuccess: eventFactory('alert-success'), diff --git a/packages/grafana-data/src/types/logs.ts b/packages/grafana-data/src/types/logs.ts index d6b4522c83c..daececb446e 100644 --- a/packages/grafana-data/src/types/logs.ts +++ b/packages/grafana-data/src/types/logs.ts @@ -1,8 +1,8 @@ import { Labels } from './data'; import { DataFrame } from './dataFrame'; +import { DataQueryResponse } from './datasource'; import { DataQuery } from './query'; import { AbsoluteTimeRange } from './time'; -import { DataQueryResponse } from './datasource'; /** * Mapping of log level abbreviation to canonical log level. diff --git a/packages/grafana-data/src/types/logsVolume.ts b/packages/grafana-data/src/types/logsVolume.ts index bbb73eef7a1..925f63d899b 100644 --- a/packages/grafana-data/src/types/logsVolume.ts +++ b/packages/grafana-data/src/types/logsVolume.ts @@ -1,7 +1,8 @@ -import { DataQuery } from './query'; -import { DataQueryRequest, DataQueryResponse } from './datasource'; import { Observable } from 'rxjs'; +import { DataQueryRequest, DataQueryResponse } from './datasource'; +import { DataQuery } from './query'; + /** * TODO: This should be added to ./logs.ts but because of cross reference between ./datasource.ts and ./logs.ts it can * be done only after decoupling "logs" from "datasource" (https://github.com/grafana/grafana/pull/39536) diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index b5a0dbdd534..9061fdf9e3e 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -1,19 +1,21 @@ -import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource'; -import { PluginMeta } from './plugin'; -import { ScopedVars } from './ScopedVars'; -import { LoadingState, PreferredVisualisationType } from './data'; -import { DataFrame, FieldType } from './dataFrame'; -import { AbsoluteTimeRange, TimeRange, TimeZone } from './time'; +import { defaultsDeep } from 'lodash'; + import { EventBus } from '../events'; -import { FieldConfigSource } from './fieldOverrides'; -import { Registry } from '../utils'; import { StandardEditorProps } from '../field'; +import { Registry } from '../utils'; + import { OptionsEditorItem } from './OptionsUIRegistryBuilder'; -import { OptionEditorConfig } from './options'; +import { ScopedVars } from './ScopedVars'; import { AlertStateInfo } from './alerts'; import { PanelModel } from './dashboard'; +import { LoadingState, PreferredVisualisationType } from './data'; +import { DataFrame, FieldType } from './dataFrame'; +import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource'; +import { FieldConfigSource } from './fieldOverrides'; +import { OptionEditorConfig } from './options'; +import { PluginMeta } from './plugin'; +import { AbsoluteTimeRange, TimeRange, TimeZone } from './time'; import { DataTransformerConfig } from './transformations'; -import { defaultsDeep } from 'lodash'; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index d2f027a1f92..a3c08442493 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -1,4 +1,5 @@ import { ComponentType } from 'react'; + import { KeyValue } from './data'; /** Describes plugins life cycle status */ diff --git a/packages/grafana-data/src/types/queryRunner.ts b/packages/grafana-data/src/types/queryRunner.ts index ef5a9100d79..09d40b0e151 100644 --- a/packages/grafana-data/src/types/queryRunner.ts +++ b/packages/grafana-data/src/types/queryRunner.ts @@ -1,8 +1,9 @@ import { Observable } from 'rxjs'; -import { DataQuery, DataSourceRef } from './query'; + +import { ScopedVars } from './ScopedVars'; import { DataSourceApi } from './datasource'; import { PanelData } from './panel'; -import { ScopedVars } from './ScopedVars'; +import { DataQuery, DataSourceRef } from './query'; import { TimeRange, TimeZone } from './time'; /** diff --git a/packages/grafana-data/src/types/transformations.ts b/packages/grafana-data/src/types/transformations.ts index 8e2ae2552f4..5af323890ac 100644 --- a/packages/grafana-data/src/types/transformations.ts +++ b/packages/grafana-data/src/types/transformations.ts @@ -1,8 +1,9 @@ import { MonoTypeOperatorFunction } from 'rxjs'; -import { DataFrame, Field } from './dataFrame'; import { RegistryItemWithOptions } from '../utils/Registry'; + import { ScopedVars } from './ScopedVars'; +import { DataFrame, Field } from './dataFrame'; /** * Function that transform data frames (AKA transformer) diff --git a/packages/grafana-data/src/types/variables.ts b/packages/grafana-data/src/types/variables.ts index 1c90dcd7b3e..a7d3087d8ff 100644 --- a/packages/grafana-data/src/types/variables.ts +++ b/packages/grafana-data/src/types/variables.ts @@ -1,6 +1,5 @@ import { ComponentType } from 'react'; import { Observable } from 'rxjs'; -import { DataQuery } from './query'; import { DataQueryRequest, @@ -11,6 +10,7 @@ import { DataSourceQueryType, QueryEditorProps, } from './datasource'; +import { DataQuery } from './query'; /** * Enum with the different variable support types diff --git a/packages/grafana-data/src/utils/OptionsUIBuilders.ts b/packages/grafana-data/src/utils/OptionsUIBuilders.ts index d6c760f04ea..3ce6b10d217 100644 --- a/packages/grafana-data/src/utils/OptionsUIBuilders.ts +++ b/packages/grafana-data/src/utils/OptionsUIBuilders.ts @@ -1,6 +1,3 @@ -import { FieldConfigEditorProps, FieldConfigPropertyItem, FieldConfigEditorConfig } from '../types/fieldOverrides'; -import { OptionsEditorItem, OptionsUIRegistryBuilder } from '../types/OptionsUIRegistryBuilder'; -import { PanelOptionsEditorConfig, PanelOptionsEditorItem } from '../types/panel'; import { numberOverrideProcessor, selectOverrideProcessor, @@ -19,6 +16,9 @@ import { StandardEditorContext, } from '../field'; import { PanelOptionsSupplier } from '../panel/PanelPlugin'; +import { OptionsEditorItem, OptionsUIRegistryBuilder } from '../types/OptionsUIRegistryBuilder'; +import { FieldConfigEditorProps, FieldConfigPropertyItem, FieldConfigEditorConfig } from '../types/fieldOverrides'; +import { PanelOptionsEditorConfig, PanelOptionsEditorItem } from '../types/panel'; /** * Fluent API for declarative creation of field config option editors diff --git a/packages/grafana-data/src/utils/Registry.test.ts b/packages/grafana-data/src/utils/Registry.test.ts index a979c2c6103..b1d30bb6bf7 100644 --- a/packages/grafana-data/src/utils/Registry.test.ts +++ b/packages/grafana-data/src/utils/Registry.test.ts @@ -1,6 +1,7 @@ -import { Registry } from './Registry'; import { FieldReducerInfo, fieldReducers, ReducerID } from '../transformations'; +import { Registry } from './Registry'; + describe('Registry', () => { describe('selectOptions', () => { describe('when called with current', () => { diff --git a/packages/grafana-data/src/utils/arrayUtils.test.ts b/packages/grafana-data/src/utils/arrayUtils.test.ts index 6f7c81aa8ea..892624c5bb2 100644 --- a/packages/grafana-data/src/utils/arrayUtils.test.ts +++ b/packages/grafana-data/src/utils/arrayUtils.test.ts @@ -1,4 +1,5 @@ import { SortOrder } from '@grafana/schema'; + import { sortValues } from './arrayUtils'; describe('arrayUtils', () => { diff --git a/packages/grafana-data/src/utils/csv.test.ts b/packages/grafana-data/src/utils/csv.test.ts index ea5f6598bf1..f6e6ff6817f 100644 --- a/packages/grafana-data/src/utils/csv.test.ts +++ b/packages/grafana-data/src/utils/csv.test.ts @@ -1,12 +1,13 @@ -import { CSVHeaderStyle, readCSV, toCSV } from './csv'; -import { getDataFrameRow, toDataFrameDTO } from '../dataframe/processDataFrame'; - // Test with local CSV files import fs from 'fs'; + import { MutableDataFrame } from '../dataframe'; +import { getDataFrameRow, toDataFrameDTO } from '../dataframe/processDataFrame'; import { getDisplayProcessor } from '../field'; import { createTheme } from '../themes'; +import { CSVHeaderStyle, readCSV, toCSV } from './csv'; + describe('read csv', () => { it('should get X and y', () => { const text = ',1\n2,3,4\n5,6\n,,,7'; diff --git a/packages/grafana-data/src/utils/csv.ts b/packages/grafana-data/src/utils/csv.ts index 11dc0be344a..6ad8c464fc7 100644 --- a/packages/grafana-data/src/utils/csv.ts +++ b/packages/grafana-data/src/utils/csv.ts @@ -1,12 +1,12 @@ // Libraries -import Papa, { ParseConfig, Parser, ParseResult } from 'papaparse'; import { defaults } from 'lodash'; +import Papa, { ParseConfig, Parser, ParseResult } from 'papaparse'; // Types -import { DataFrame, Field, FieldConfig, FieldType } from '../types'; -import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; import { MutableDataFrame } from '../dataframe/MutableDataFrame'; +import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; import { getFieldDisplayName } from '../field'; +import { DataFrame, Field, FieldConfig, FieldType } from '../types'; import { formattedValueToString } from '../valueFormats'; export enum CSVHeaderStyle { diff --git a/packages/grafana-data/src/utils/dataLinks.test.ts b/packages/grafana-data/src/utils/dataLinks.test.ts index b609ed2827b..93a249a1df7 100644 --- a/packages/grafana-data/src/utils/dataLinks.test.ts +++ b/packages/grafana-data/src/utils/dataLinks.test.ts @@ -1,7 +1,8 @@ -import { mapInternalLinkToExplore } from './dataLinks'; import { DataLink, FieldType } from '../types'; import { ArrayVector } from '../vector'; +import { mapInternalLinkToExplore } from './dataLinks'; + describe('mapInternalLinkToExplore', () => { it('creates internal link', () => { const dataLink = { diff --git a/packages/grafana-data/src/utils/dataLinks.ts b/packages/grafana-data/src/utils/dataLinks.ts index ce279fe5421..71dabe78e51 100644 --- a/packages/grafana-data/src/utils/dataLinks.ts +++ b/packages/grafana-data/src/utils/dataLinks.ts @@ -10,6 +10,7 @@ import { SplitOpen, TimeRange, } from '../types'; + import { locationUtil } from './location'; import { serializeStateToUrlParam } from './url'; diff --git a/packages/grafana-data/src/utils/datasource.ts b/packages/grafana-data/src/utils/datasource.ts index a57f51a1bdd..95f5ec9af0d 100644 --- a/packages/grafana-data/src/utils/datasource.ts +++ b/packages/grafana-data/src/utils/datasource.ts @@ -1,4 +1,5 @@ import { isString } from 'lodash'; + import { DataSourcePluginOptionsEditorProps, SelectableValue, diff --git a/packages/grafana-data/src/utils/fieldParser.ts b/packages/grafana-data/src/utils/fieldParser.ts index 939f4e401f2..1763cef86ac 100644 --- a/packages/grafana-data/src/utils/fieldParser.ts +++ b/packages/grafana-data/src/utils/fieldParser.ts @@ -1,5 +1,5 @@ -import { Field, FieldType } from '../types/dataFrame'; import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; +import { Field, FieldType } from '../types/dataFrame'; export function makeFieldParser(value: any, field: Field): (value: string) => any { if (!field.type) { diff --git a/packages/grafana-data/src/utils/flotPairs.test.ts b/packages/grafana-data/src/utils/flotPairs.test.ts index 69dc3e137a3..1ab5d314b2c 100644 --- a/packages/grafana-data/src/utils/flotPairs.test.ts +++ b/packages/grafana-data/src/utils/flotPairs.test.ts @@ -1,7 +1,8 @@ import { MutableDataFrame } from '../dataframe/MutableDataFrame'; -import { getFlotPairs, getFlotPairsConstant } from './flotPairs'; -import { TimeRange } from '../types/time'; import { dateTime } from '../datetime/moment_wrapper'; +import { TimeRange } from '../types/time'; + +import { getFlotPairs, getFlotPairsConstant } from './flotPairs'; describe('getFlotPairs', () => { const series = new MutableDataFrame({ diff --git a/packages/grafana-data/src/utils/flotPairs.ts b/packages/grafana-data/src/utils/flotPairs.ts index 7f99dd11fdb..f17c992742a 100644 --- a/packages/grafana-data/src/utils/flotPairs.ts +++ b/packages/grafana-data/src/utils/flotPairs.ts @@ -1,5 +1,5 @@ -import { Field } from '../types/dataFrame'; import { NullValueMode } from '../types/data'; +import { Field } from '../types/dataFrame'; import { GraphSeriesValue } from '../types/graph'; import { TimeRange } from '../types/time'; diff --git a/packages/grafana-data/src/utils/labels.test.ts b/packages/grafana-data/src/utils/labels.test.ts index d70e9847be6..5bf7d79a093 100644 --- a/packages/grafana-data/src/utils/labels.test.ts +++ b/packages/grafana-data/src/utils/labels.test.ts @@ -1,6 +1,7 @@ -import { parseLabels, formatLabels, findCommonLabels, findUniqueLabels, matchAllLabels } from './labels'; import { Labels } from '../types/data'; +import { parseLabels, formatLabels, findCommonLabels, findUniqueLabels, matchAllLabels } from './labels'; + describe('parseLabels()', () => { it('returns no labels on empty labels string', () => { expect(parseLabels('')).toEqual({}); diff --git a/packages/grafana-data/src/utils/location.ts b/packages/grafana-data/src/utils/location.ts index 7df8796b41b..ee538687394 100644 --- a/packages/grafana-data/src/utils/location.ts +++ b/packages/grafana-data/src/utils/location.ts @@ -1,7 +1,9 @@ import { Location } from 'history'; -import { GrafanaConfig, RawTimeRange, ScopedVars } from '../types'; -import { UrlQueryMap, urlUtil } from './url'; + import { textUtil } from '../text'; +import { GrafanaConfig, RawTimeRange, ScopedVars } from '../types'; + +import { UrlQueryMap, urlUtil } from './url'; let grafanaConfig: GrafanaConfig = { appSubUrl: '' } as any; let getTimeRangeUrlParams: () => RawTimeRange; diff --git a/packages/grafana-data/src/utils/logs.test.ts b/packages/grafana-data/src/utils/logs.test.ts index 11f1248b0c7..cc2387aeec5 100644 --- a/packages/grafana-data/src/utils/logs.test.ts +++ b/packages/grafana-data/src/utils/logs.test.ts @@ -1,5 +1,6 @@ -import { LogLevel, LogsModel, LogRowModel, LogsSortOrder } from '../types/logs'; import { MutableDataFrame } from '../dataframe/MutableDataFrame'; +import { LogLevel, LogsModel, LogRowModel, LogsSortOrder } from '../types/logs'; + import { getLogLevel, calculateLogsLabelStats, diff --git a/packages/grafana-data/src/utils/logs.ts b/packages/grafana-data/src/utils/logs.ts index a95d14fc42e..7fa62feb5d2 100644 --- a/packages/grafana-data/src/utils/logs.ts +++ b/packages/grafana-data/src/utils/logs.ts @@ -1,7 +1,7 @@ import { countBy, chain, escapeRegExp } from 'lodash'; -import { LogLevel, LogRowModel, LogLabelStatsModel, LogsParser, LogsModel, LogsSortOrder } from '../types/logs'; import { DataFrame, FieldType } from '../types/index'; +import { LogLevel, LogRowModel, LogLabelStatsModel, LogsParser, LogsModel, LogsSortOrder } from '../types/logs'; import { ArrayVector } from '../vector/ArrayVector'; // This matches: diff --git a/packages/grafana-data/src/utils/namedColorsPalette.test.ts b/packages/grafana-data/src/utils/namedColorsPalette.test.ts index 2ee9ab1437e..debb0630409 100644 --- a/packages/grafana-data/src/utils/namedColorsPalette.test.ts +++ b/packages/grafana-data/src/utils/namedColorsPalette.test.ts @@ -1,6 +1,7 @@ -import { getColorForTheme } from './namedColorsPalette'; import { createTheme } from '../themes'; +import { getColorForTheme } from './namedColorsPalette'; + describe('colors', () => { const theme = createTheme(); diff --git a/packages/grafana-data/src/utils/series.test.ts b/packages/grafana-data/src/utils/series.test.ts index 4ef77d80726..5ab3d843a14 100644 --- a/packages/grafana-data/src/utils/series.test.ts +++ b/packages/grafana-data/src/utils/series.test.ts @@ -1,7 +1,8 @@ -import { getSeriesTimeStep, hasMsResolution } from './series'; import { Field, FieldType } from '../types'; import { ArrayVector } from '../vector'; +import { getSeriesTimeStep, hasMsResolution } from './series'; + const uniformTimeField: Field = { name: 'time', type: FieldType.time, diff --git a/packages/grafana-data/src/utils/valueMappings.test.ts b/packages/grafana-data/src/utils/valueMappings.test.ts index 11e76aefc94..8acee459ce7 100644 --- a/packages/grafana-data/src/utils/valueMappings.test.ts +++ b/packages/grafana-data/src/utils/valueMappings.test.ts @@ -1,6 +1,7 @@ -import { getValueMappingResult, isNumeric } from './valueMappings'; import { ValueMapping, MappingType, SpecialValueMatch } from '../types'; +import { getValueMappingResult, isNumeric } from './valueMappings'; + const testSet1: ValueMapping[] = [ { type: MappingType.ValueToText, diff --git a/packages/grafana-data/src/utils/valueMappings.ts b/packages/grafana-data/src/utils/valueMappings.ts index e774d058016..3cc2a79c1ec 100644 --- a/packages/grafana-data/src/utils/valueMappings.ts +++ b/packages/grafana-data/src/utils/valueMappings.ts @@ -1,3 +1,5 @@ +import { getActiveThreshold } from '../field'; +import { stringToJsRegex } from '../text/string'; import { MappingType, SpecialValueMatch, @@ -7,8 +9,6 @@ import { ValueMappingResult, SpecialValueOptions, } from '../types'; -import { getActiveThreshold } from '../field'; -import { stringToJsRegex } from '../text/string'; export function getValueMappingResult(valueMappings: ValueMapping[], value: any): ValueMappingResult | null { for (const vm of valueMappings) { diff --git a/packages/grafana-data/src/valueFormats/arithmeticFormatters.ts b/packages/grafana-data/src/valueFormats/arithmeticFormatters.ts index 6aca412f113..a28e2ee7405 100644 --- a/packages/grafana-data/src/valueFormats/arithmeticFormatters.ts +++ b/packages/grafana-data/src/valueFormats/arithmeticFormatters.ts @@ -1,6 +1,7 @@ -import { toFixed, FormattedValue } from './valueFormats'; import { DecimalCount } from '../types/displayValue'; +import { toFixed, FormattedValue } from './valueFormats'; + export function toPercent(size: number, decimals: DecimalCount): FormattedValue { if (size === null) { return { text: '' }; diff --git a/packages/grafana-data/src/valueFormats/categories.ts b/packages/grafana-data/src/valueFormats/categories.ts index 2b3340f9e97..dd6e2455908 100644 --- a/packages/grafana-data/src/valueFormats/categories.ts +++ b/packages/grafana-data/src/valueFormats/categories.ts @@ -1,12 +1,4 @@ -import { - locale, - scaledUnits, - simpleCountUnit, - toFixedUnit, - ValueFormatCategory, - stringFormater, - booleanValueFormatter, -} from './valueFormats'; +import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters'; import { dateTimeAsIso, dateTimeAsIsoNoDateIfToday, @@ -31,8 +23,16 @@ import { toTimeTicks, dateTimeSystemFormatter, } from './dateTimeFormatters'; -import { toHex, sci, toHex0x, toPercent, toPercentUnit } from './arithmeticFormatters'; import { binaryPrefix, currency, SIPrefix } from './symbolFormatters'; +import { + locale, + scaledUnits, + simpleCountUnit, + toFixedUnit, + ValueFormatCategory, + stringFormater, + booleanValueFormatter, +} from './valueFormats'; export const getCategories = (): ValueFormatCategory[] => [ { diff --git a/packages/grafana-data/src/valueFormats/dateTimeFormatters.test.ts b/packages/grafana-data/src/valueFormats/dateTimeFormatters.test.ts index 80b6b9123b5..d61beabce0d 100644 --- a/packages/grafana-data/src/valueFormats/dateTimeFormatters.test.ts +++ b/packages/grafana-data/src/valueFormats/dateTimeFormatters.test.ts @@ -1,3 +1,5 @@ +import { toUtc, dateTime } from '../datetime/moment_wrapper'; + import { dateTimeAsIso, dateTimeAsIsoNoDateIfToday, @@ -17,7 +19,6 @@ import { toSeconds, } from './dateTimeFormatters'; import { formattedValueToString } from './valueFormats'; -import { toUtc, dateTime } from '../datetime/moment_wrapper'; describe('date time formats', () => { const epoch = 1505634997920; diff --git a/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts b/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts index 3f95589d5c3..408f85c5e73 100644 --- a/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts +++ b/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts @@ -1,9 +1,9 @@ +import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat, systemDateFormats } from '../datetime'; import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrapper'; +import { TimeZone } from '../types'; +import { DecimalCount } from '../types/displayValue'; import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats'; -import { DecimalCount } from '../types/displayValue'; -import { TimeZone } from '../types'; -import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat, systemDateFormats } from '../datetime'; interface IntervalsInSeconds { [interval: string]: number; diff --git a/packages/grafana-data/src/valueFormats/symbolFormatters.ts b/packages/grafana-data/src/valueFormats/symbolFormatters.ts index 54c96bfb966..cb59e83f464 100644 --- a/packages/grafana-data/src/valueFormats/symbolFormatters.ts +++ b/packages/grafana-data/src/valueFormats/symbolFormatters.ts @@ -1,6 +1,7 @@ -import { scaledUnits, ValueFormatter } from './valueFormats'; import { DecimalCount } from '../types/displayValue'; +import { scaledUnits, ValueFormatter } from './valueFormats'; + export function currency(symbol: string, asSuffix?: boolean): ValueFormatter { const units = ['', 'K', 'M', 'B', 'T']; const scaler = scaledUnits(1000, units); diff --git a/packages/grafana-data/src/valueFormats/valueFormats.test.ts b/packages/grafana-data/src/valueFormats/valueFormats.test.ts index 3aedf0f42bc..202eeccfe4d 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.test.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.test.ts @@ -1,7 +1,8 @@ -import { toFixed, getValueFormat, scaledUnits, formattedValueToString } from './valueFormats'; -import { DecimalCount } from '../types/displayValue'; -import { TimeZone } from '../types'; import { dateTime } from '../datetime'; +import { TimeZone } from '../types'; +import { DecimalCount } from '../types/displayValue'; + +import { toFixed, getValueFormat, scaledUnits, formattedValueToString } from './valueFormats'; interface ValueFormatTest { id: string; diff --git a/packages/grafana-data/src/valueFormats/valueFormats.ts b/packages/grafana-data/src/valueFormats/valueFormats.ts index 76a38232cdf..c0ea18dfd31 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.ts @@ -1,8 +1,9 @@ -import { getCategories } from './categories'; +import { TimeZone } from '../types'; import { DecimalCount } from '../types/displayValue'; + +import { getCategories } from './categories'; import { toDateTimeValueFormatter } from './dateTimeFormatters'; import { getOffsetFromSIPrefix, SIPrefix, currency } from './symbolFormatters'; -import { TimeZone } from '../types'; export interface FormattedValue { text: string; diff --git a/packages/grafana-data/src/vector/AppendedVectors.test.ts b/packages/grafana-data/src/vector/AppendedVectors.test.ts index 9d39b47ce09..00c1f4272b2 100644 --- a/packages/grafana-data/src/vector/AppendedVectors.test.ts +++ b/packages/grafana-data/src/vector/AppendedVectors.test.ts @@ -1,5 +1,5 @@ -import { ArrayVector } from './ArrayVector'; import { AppendedVectors } from './AppendedVectors'; +import { ArrayVector } from './ArrayVector'; describe('Check Appending Vector', () => { it('should transparently join them', () => { diff --git a/packages/grafana-data/src/vector/AppendedVectors.ts b/packages/grafana-data/src/vector/AppendedVectors.ts index 7d806cfbbd2..cc33811f731 100644 --- a/packages/grafana-data/src/vector/AppendedVectors.ts +++ b/packages/grafana-data/src/vector/AppendedVectors.ts @@ -1,4 +1,5 @@ import { Vector } from '../types/vector'; + import { vectorToArray } from './vectorToArray'; interface AppendedVectorInfo { diff --git a/packages/grafana-data/src/vector/ArrayVector.ts b/packages/grafana-data/src/vector/ArrayVector.ts index 3df650e3044..d153cfb636c 100644 --- a/packages/grafana-data/src/vector/ArrayVector.ts +++ b/packages/grafana-data/src/vector/ArrayVector.ts @@ -1,4 +1,5 @@ import { MutableVector } from '../types/vector'; + import { FunctionalVector } from './FunctionalVector'; /** diff --git a/packages/grafana-data/src/vector/AsNumberVector.ts b/packages/grafana-data/src/vector/AsNumberVector.ts index c8a65916a3f..775068dbe45 100644 --- a/packages/grafana-data/src/vector/AsNumberVector.ts +++ b/packages/grafana-data/src/vector/AsNumberVector.ts @@ -1,4 +1,5 @@ import { Vector } from '../types'; + import { FunctionalVector } from './FunctionalVector'; /** diff --git a/packages/grafana-data/src/vector/BinaryOperationVector.test.ts b/packages/grafana-data/src/vector/BinaryOperationVector.test.ts index c6d9955cbaf..d26ed06d3bb 100644 --- a/packages/grafana-data/src/vector/BinaryOperationVector.test.ts +++ b/packages/grafana-data/src/vector/BinaryOperationVector.test.ts @@ -1,7 +1,8 @@ +import { binaryOperators, BinaryOperationID } from '../utils/binaryOperators'; + import { ArrayVector } from './ArrayVector'; import { BinaryOperationVector } from './BinaryOperationVector'; import { ConstantVector } from './ConstantVector'; -import { binaryOperators, BinaryOperationID } from '../utils/binaryOperators'; describe('ScaledVector', () => { it('should support multiply operations', () => { diff --git a/packages/grafana-data/src/vector/BinaryOperationVector.ts b/packages/grafana-data/src/vector/BinaryOperationVector.ts index 1740f99bb72..a8ea3024bec 100644 --- a/packages/grafana-data/src/vector/BinaryOperationVector.ts +++ b/packages/grafana-data/src/vector/BinaryOperationVector.ts @@ -1,7 +1,8 @@ import { Vector } from '../types/vector'; -import { vectorToArray } from './vectorToArray'; import { BinaryOperation } from '../utils/binaryOperators'; +import { vectorToArray } from './vectorToArray'; + /** * @public */ diff --git a/packages/grafana-data/src/vector/CircularVector.ts b/packages/grafana-data/src/vector/CircularVector.ts index 3f3b1f6c95e..6d848ad40f4 100644 --- a/packages/grafana-data/src/vector/CircularVector.ts +++ b/packages/grafana-data/src/vector/CircularVector.ts @@ -1,6 +1,7 @@ import { MutableVector } from '../types/vector'; -import { vectorToArray } from './vectorToArray'; + import { FunctionalVector } from './FunctionalVector'; +import { vectorToArray } from './vectorToArray'; interface CircularOptions { buffer?: T[]; diff --git a/packages/grafana-data/src/vector/FormattedVector.ts b/packages/grafana-data/src/vector/FormattedVector.ts index f30e958026d..277342173ed 100644 --- a/packages/grafana-data/src/vector/FormattedVector.ts +++ b/packages/grafana-data/src/vector/FormattedVector.ts @@ -1,6 +1,7 @@ -import { Vector } from '../types/vector'; import { DisplayProcessor } from '../types'; +import { Vector } from '../types/vector'; import { formattedValueToString } from '../valueFormats'; + import { FunctionalVector } from './FunctionalVector'; /** diff --git a/packages/grafana-data/src/vector/FunctionalVector.ts b/packages/grafana-data/src/vector/FunctionalVector.ts index 6a6c719be2a..b9f4e22907b 100644 --- a/packages/grafana-data/src/vector/FunctionalVector.ts +++ b/packages/grafana-data/src/vector/FunctionalVector.ts @@ -1,6 +1,7 @@ -import { vectorToArray } from './vectorToArray'; import { Vector } from '../types'; +import { vectorToArray } from './vectorToArray'; + /** @public */ export abstract class FunctionalVector implements Vector, Iterable { abstract get length(): number; diff --git a/packages/grafana-data/src/vector/IndexVector.ts b/packages/grafana-data/src/vector/IndexVector.ts index df695fc0e6c..4a7fc3dfc27 100644 --- a/packages/grafana-data/src/vector/IndexVector.ts +++ b/packages/grafana-data/src/vector/IndexVector.ts @@ -1,4 +1,5 @@ import { Field, FieldType } from '../types'; + import { FunctionalVector } from './FunctionalVector'; /** diff --git a/packages/grafana-data/src/vector/RowVector.ts b/packages/grafana-data/src/vector/RowVector.ts index d05904c28b3..21746be48fd 100644 --- a/packages/grafana-data/src/vector/RowVector.ts +++ b/packages/grafana-data/src/vector/RowVector.ts @@ -1,4 +1,5 @@ import { Vector } from '../types'; + import { vectorToArray } from './vectorToArray'; /** diff --git a/packages/grafana-data/src/vector/SortedVector.ts b/packages/grafana-data/src/vector/SortedVector.ts index 33b97f6778f..0d66dff6ded 100644 --- a/packages/grafana-data/src/vector/SortedVector.ts +++ b/packages/grafana-data/src/vector/SortedVector.ts @@ -1,4 +1,5 @@ import { Vector } from '../types/vector'; + import { vectorToArray } from './vectorToArray'; /** diff --git a/packages/grafana-e2e-selectors/src/selectors/index.ts b/packages/grafana-e2e-selectors/src/selectors/index.ts index 7f82a08b6ce..5cedeb04627 100644 --- a/packages/grafana-e2e-selectors/src/selectors/index.ts +++ b/packages/grafana-e2e-selectors/src/selectors/index.ts @@ -1,7 +1,8 @@ -import { Pages } from './pages'; -import { Components } from './components'; import { E2ESelectors } from '../types'; +import { Components } from './components'; +import { Pages } from './pages'; + /** * Exposes selectors in package for easy use in e2e tests and in production code * diff --git a/packages/grafana-e2e/cli.js b/packages/grafana-e2e/cli.js index 88dedf61da9..c2d42b204e4 100644 --- a/packages/grafana-e2e/cli.js +++ b/packages/grafana-e2e/cli.js @@ -1,7 +1,7 @@ -const execa = require('execa'); const { program } = require('commander'); -const resolveBin = require('resolve-as-bin'); +const execa = require('execa'); const { resolve, sep } = require('path'); +const resolveBin = require('resolve-as-bin'); const cypress = (commandName, { updateScreenshots }) => { // Support running an unpublished dev build diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts b/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts index b3f59028d06..76e120a741c 100644 --- a/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts +++ b/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts @@ -1,8 +1,8 @@ import CDP from 'chrome-remote-interface'; +import ProtocolProxyApi from 'devtools-protocol/types/protocol-proxy-api'; +import { countBy, mean } from 'lodash'; import Tracelib, { TraceEvent } from 'tracelib'; -import { countBy, mean } from 'lodash'; -import ProtocolProxyApi from 'devtools-protocol/types/protocol-proxy-api'; import { CollectedData, DataCollector, DataCollectorName } from './DataCollector'; type CDPDataCollectorDeps = { diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts b/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts index 8372309f50f..3ecb983a027 100644 --- a/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts +++ b/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts @@ -1,6 +1,7 @@ -import { CollectedData, DataCollectorName } from './DataCollector'; import { fromPairs } from 'lodash'; +import { CollectedData, DataCollectorName } from './DataCollector'; + type Stats = { sum: number; min: number; diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/index.ts b/packages/grafana-e2e/cypress/plugins/benchmark/index.ts index d822b40dc67..22307555d58 100644 --- a/packages/grafana-e2e/cypress/plugins/benchmark/index.ts +++ b/packages/grafana-e2e/cypress/plugins/benchmark/index.ts @@ -1,7 +1,8 @@ -import { CollectedData, DataCollector } from './DataCollector'; -import { CDPDataCollector } from './CDPDataCollector'; -import { fromPairs } from 'lodash'; import fs from 'fs'; +import { fromPairs } from 'lodash'; + +import { CDPDataCollector } from './CDPDataCollector'; +import { CollectedData, DataCollector } from './DataCollector'; import { formatResults } from './formatting'; const remoteDebuggingPortOptionPrefix = '--remote-debugging-port='; diff --git a/packages/grafana-e2e/cypress/plugins/index.js b/packages/grafana-e2e/cypress/plugins/index.js index d9c4eebba76..9b3125623ed 100644 --- a/packages/grafana-e2e/cypress/plugins/index.js +++ b/packages/grafana-e2e/cypress/plugins/index.js @@ -1,11 +1,11 @@ const fs = require('fs'); const path = require('path'); +const benchmarkPlugin = require('./benchmark'); const compareScreenshots = require('./compareScreenshots'); const extendConfig = require('./extendConfig'); const readProvisions = require('./readProvisions'); const typescriptPreprocessor = require('./typescriptPreprocessor'); -const benchmarkPlugin = require('./benchmark'); module.exports = (on, config) => { if (config.env['BENCHMARK_PLUGIN_ENABLED'] === true) { diff --git a/packages/grafana-e2e/cypress/plugins/readProvisions.js b/packages/grafana-e2e/cypress/plugins/readProvisions.js index 92f6c16c223..f82542d2070 100644 --- a/packages/grafana-e2e/cypress/plugins/readProvisions.js +++ b/packages/grafana-e2e/cypress/plugins/readProvisions.js @@ -1,9 +1,9 @@ 'use strict'; -const { parse: parseYml } = require('yaml'); const { promises: { readFile }, } = require('fs'); const { resolve: resolvePath } = require('path'); +const { parse: parseYml } = require('yaml'); const readProvision = (filePath) => readFile(filePath, 'utf8').then((contents) => parseYml(contents)); diff --git a/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js b/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js index 8de33d53e2f..be506578188 100644 --- a/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js +++ b/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js @@ -1,5 +1,5 @@ -const { resolve } = require('path'); const wp = require('@cypress/webpack-preprocessor'); +const { resolve } = require('path'); const anyNodeModules = /node_modules/; const packageRoot = resolve(`${__dirname}/../../`); diff --git a/packages/grafana-e2e/rollup.config.ts b/packages/grafana-e2e/rollup.config.ts index 341c18b8719..b3189469b61 100644 --- a/packages/grafana-e2e/rollup.config.ts +++ b/packages/grafana-e2e/rollup.config.ts @@ -1,5 +1,5 @@ -import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; import copy from 'rollup-plugin-copy'; import sourceMaps from 'rollup-plugin-sourcemaps'; import { terser } from 'rollup-plugin-terser'; diff --git a/packages/grafana-e2e/src/flows/addDashboard.ts b/packages/grafana-e2e/src/flows/addDashboard.ts index d7efeb9c9ba..252f9b79156 100644 --- a/packages/grafana-e2e/src/flows/addDashboard.ts +++ b/packages/grafana-e2e/src/flows/addDashboard.ts @@ -1,9 +1,11 @@ -import { DeleteDashboardConfig } from './deleteDashboard'; +import { v4 as uuidv4 } from 'uuid'; + import { e2e } from '../index'; import { getDashboardUid } from '../support/url'; -import { setDashboardTimeRange, TimeRangeConfig } from './setDashboardTimeRange'; -import { v4 as uuidv4 } from 'uuid'; + +import { DeleteDashboardConfig } from './deleteDashboard'; import { selectOption } from './selectOption'; +import { setDashboardTimeRange, TimeRangeConfig } from './setDashboardTimeRange'; export interface AddAnnotationConfig { dataSource: string; diff --git a/packages/grafana-e2e/src/flows/addDataSource.ts b/packages/grafana-e2e/src/flows/addDataSource.ts index bb507f12c49..5c6a9fdcd5e 100644 --- a/packages/grafana-e2e/src/flows/addDataSource.ts +++ b/packages/grafana-e2e/src/flows/addDataSource.ts @@ -1,7 +1,9 @@ -import { DeleteDataSourceConfig } from './deleteDataSource'; -import { e2e } from '../index'; import { v4 as uuidv4 } from 'uuid'; +import { e2e } from '../index'; + +import { DeleteDataSourceConfig } from './deleteDataSource'; + export interface AddDataSourceConfig { basicAuth: boolean; basicAuthPassword: string; diff --git a/packages/grafana-e2e/src/flows/addPanel.ts b/packages/grafana-e2e/src/flows/addPanel.ts index f24029c400b..0f214e51835 100644 --- a/packages/grafana-e2e/src/flows/addPanel.ts +++ b/packages/grafana-e2e/src/flows/addPanel.ts @@ -1,7 +1,9 @@ -import { configurePanel, PartialAddPanelConfig } from './configurePanel'; -import { getScenarioContext } from '../support/scenarioContext'; import { v4 as uuidv4 } from 'uuid'; +import { getScenarioContext } from '../support/scenarioContext'; + +import { configurePanel, PartialAddPanelConfig } from './configurePanel'; + export const addPanel = (config?: Partial) => getScenarioContext().then(({ lastAddedDataSource }: any) => configurePanel({ diff --git a/packages/grafana-e2e/src/flows/configurePanel.ts b/packages/grafana-e2e/src/flows/configurePanel.ts index 182bdaab287..384bef3200c 100644 --- a/packages/grafana-e2e/src/flows/configurePanel.ts +++ b/packages/grafana-e2e/src/flows/configurePanel.ts @@ -1,5 +1,6 @@ -import { e2e } from '../index'; +import { e2e } from '..'; import { getScenarioContext } from '../support/scenarioContext'; + import { selectOption } from './selectOption'; import { setDashboardTimeRange } from './setDashboardTimeRange'; import { TimeRangeConfig } from './setTimeRange'; @@ -147,7 +148,7 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC //e2e().wait('@chartData'); // Avoid annotations flakiness - e2e.components.RefreshPicker.runButton().should('be.visible').click(); + e2e.components.RefreshPicker.runButtonV2().first().should('be.visible').click({ force: true }); e2e().wait('@chartData'); diff --git a/packages/grafana-e2e/src/flows/importDashboard.ts b/packages/grafana-e2e/src/flows/importDashboard.ts index c054dd4ad3a..3b4e37371f1 100644 --- a/packages/grafana-e2e/src/flows/importDashboard.ts +++ b/packages/grafana-e2e/src/flows/importDashboard.ts @@ -1,7 +1,8 @@ -import { DeleteDashboardConfig } from '.'; import { e2e } from '../index'; import { fromBaseUrl, getDashboardUid } from '../support/url'; +import { DeleteDashboardConfig } from '.'; + type Panel = { title: string; [key: string]: unknown; diff --git a/packages/grafana-e2e/src/flows/importDashboards.ts b/packages/grafana-e2e/src/flows/importDashboards.ts index 24b7bd356f8..90a126b947a 100644 --- a/packages/grafana-e2e/src/flows/importDashboards.ts +++ b/packages/grafana-e2e/src/flows/importDashboards.ts @@ -1,6 +1,7 @@ -import { importDashboard, Dashboard } from './importDashboard'; import { e2e } from '../index'; +import { importDashboard, Dashboard } from './importDashboard'; + /** * Smoke test several dashboard json files from a test directory * and validate that all the panels in each import finish loading their queries diff --git a/packages/grafana-e2e/src/flows/openDashboard.ts b/packages/grafana-e2e/src/flows/openDashboard.ts index 67c9544b8fe..c535099e63a 100644 --- a/packages/grafana-e2e/src/flows/openDashboard.ts +++ b/packages/grafana-e2e/src/flows/openDashboard.ts @@ -1,5 +1,6 @@ import { e2e } from '../index'; import { getScenarioContext } from '../support/scenarioContext'; + import { setDashboardTimeRange, TimeRangeConfig } from './setDashboardTimeRange'; interface OpenDashboardDefault { diff --git a/packages/grafana-e2e/src/flows/setTimeRange.ts b/packages/grafana-e2e/src/flows/setTimeRange.ts index bcf719087d8..ad5b1d84081 100644 --- a/packages/grafana-e2e/src/flows/setTimeRange.ts +++ b/packages/grafana-e2e/src/flows/setTimeRange.ts @@ -1,4 +1,5 @@ import { e2e } from '../index'; + import { selectOption } from './selectOption'; export interface TimeRangeConfig { diff --git a/packages/grafana-e2e/src/index.ts b/packages/grafana-e2e/src/index.ts index b4c62c16352..58cacd133cf 100644 --- a/packages/grafana-e2e/src/index.ts +++ b/packages/grafana-e2e/src/index.ts @@ -3,12 +3,13 @@ * * @packageDocumentation */ -import { e2eScenario, ScenarioArguments } from './support/scenario'; -import { benchmark } from './support/benchmark'; -import { getScenarioContext, setScenarioContext } from './support/scenarioContext'; -import { e2eFactory } from './support'; import { E2ESelectors, Selectors, selectors } from '@grafana/e2e-selectors'; + import * as flows from './flows'; +import { e2eFactory } from './support'; +import { benchmark } from './support/benchmark'; +import { e2eScenario, ScenarioArguments } from './support/scenario'; +import { getScenarioContext, setScenarioContext } from './support/scenarioContext'; import * as typings from './typings'; const e2eObject = { diff --git a/packages/grafana-e2e/src/support/scenarioContext.ts b/packages/grafana-e2e/src/support/scenarioContext.ts index eeba3776c6e..510408a2b56 100644 --- a/packages/grafana-e2e/src/support/scenarioContext.ts +++ b/packages/grafana-e2e/src/support/scenarioContext.ts @@ -1,6 +1,6 @@ -import { e2e } from '../index'; import { DeleteDashboardConfig } from '../flows/deleteDashboard'; import { DeleteDataSourceConfig } from '../flows/deleteDataSource'; +import { e2e } from '../index'; export interface ScenarioContext { addedDashboards: DeleteDashboardConfig[]; diff --git a/packages/grafana-e2e/src/support/types.ts b/packages/grafana-e2e/src/support/types.ts index c0db64b11bb..e44a978194c 100644 --- a/packages/grafana-e2e/src/support/types.ts +++ b/packages/grafana-e2e/src/support/types.ts @@ -1,5 +1,7 @@ import { CssSelector, FunctionSelector, Selectors, StringSelector, UrlSelector } from '@grafana/e2e-selectors'; + import { e2e } from '../index'; + import { Selector } from './selector'; import { fromBaseUrl } from './url'; diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 5f22ae36d8c..e81c2487311 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -40,7 +40,7 @@ "@rollup/plugin-node-resolve": "13.1.3", "@testing-library/dom": "8.13.0", "@testing-library/react": "12.1.4", - "@testing-library/user-event": "^13.5.0", + "@testing-library/user-event": "14.1.1", "@types/angular": "1.8.4", "@types/history": "4.7.11", "@types/jest": "27.4.1", diff --git a/packages/grafana-runtime/rollup.config.ts b/packages/grafana-runtime/rollup.config.ts index 5fa32f856b1..c0bd7cbd7a5 100644 --- a/packages/grafana-runtime/rollup.config.ts +++ b/packages/grafana-runtime/rollup.config.ts @@ -1,5 +1,5 @@ -import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; import sourceMaps from 'rollup-plugin-sourcemaps'; import { terser } from 'rollup-plugin-terser'; diff --git a/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx b/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx index 6f18fe2141a..e41eaeaa4f0 100644 --- a/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx +++ b/packages/grafana-runtime/src/components/DataSourcePicker.test.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { DataSourcePicker } from './DataSourcePicker'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { DataSourcePicker } from './DataSourcePicker'; jest.mock('../services/dataSourceSrv'); @@ -12,7 +13,7 @@ describe('DataSourcePicker', () => { const select = render(); const clearButton = select.getByLabelText('select-clear-value'); - userEvent.click(clearButton); + await userEvent.click(clearButton); expect(onClear).toHaveBeenCalled(); }); diff --git a/packages/grafana-runtime/src/components/DataSourcePicker.tsx b/packages/grafana-runtime/src/components/DataSourcePicker.tsx index 1d71963d9a7..fd0ddb7f307 100644 --- a/packages/grafana-runtime/src/components/DataSourcePicker.tsx +++ b/packages/grafana-runtime/src/components/DataSourcePicker.tsx @@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'; // Components -import { ActionMeta, HorizontalGroup, PluginSignatureBadge, Select } from '@grafana/ui'; import { DataSourceInstanceSettings, DataSourceRef, @@ -11,6 +10,8 @@ import { SelectableValue, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { ActionMeta, HorizontalGroup, PluginSignatureBadge, Select } from '@grafana/ui'; + import { getDataSourceSrv } from '../services/dataSourceSrv'; /** diff --git a/packages/grafana-runtime/src/components/PanelDataErrorView.tsx b/packages/grafana-runtime/src/components/PanelDataErrorView.tsx index 2e3907c9264..61b88a63032 100644 --- a/packages/grafana-runtime/src/components/PanelDataErrorView.tsx +++ b/packages/grafana-runtime/src/components/PanelDataErrorView.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { FieldConfigSource, PanelData } from '@grafana/data'; /** diff --git a/packages/grafana-runtime/src/components/PanelRenderer.tsx b/packages/grafana-runtime/src/components/PanelRenderer.tsx index a2f8cc341cd..374ac9bb5fe 100644 --- a/packages/grafana-runtime/src/components/PanelRenderer.tsx +++ b/packages/grafana-runtime/src/components/PanelRenderer.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { AbsoluteTimeRange, FieldConfigSource, PanelData } from '@grafana/data'; /** diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index d5076e5ae6a..a37143b9de8 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -1,4 +1,5 @@ import { merge } from 'lodash'; + import { BootData, BuildInfo, diff --git a/packages/grafana-runtime/src/services/LocationService.ts b/packages/grafana-runtime/src/services/LocationService.ts index e76cc4c57d7..d09b4590447 100644 --- a/packages/grafana-runtime/src/services/LocationService.ts +++ b/packages/grafana-runtime/src/services/LocationService.ts @@ -1,9 +1,12 @@ -import { deprecationWarning, UrlQueryMap, urlUtil } from '@grafana/data'; import * as H from 'history'; -import { LocationUpdate } from './LocationSrv'; + +import { deprecationWarning, UrlQueryMap, urlUtil } from '@grafana/data'; import { attachDebugger, createLogger } from '@grafana/ui'; + import { config } from '../config'; +import { LocationUpdate } from './LocationSrv'; + /** * @public * A wrapper to help work with browser location and history diff --git a/packages/grafana-runtime/src/services/live.ts b/packages/grafana-runtime/src/services/live.ts index a29973a0644..f452cf35e04 100644 --- a/packages/grafana-runtime/src/services/live.ts +++ b/packages/grafana-runtime/src/services/live.ts @@ -1,3 +1,5 @@ +import { Observable } from 'rxjs'; + import { DataFrameJSON, DataQueryRequest, @@ -6,7 +8,6 @@ import { LiveChannelEvent, LiveChannelPresenceStatus, } from '@grafana/data'; -import { Observable } from 'rxjs'; /** * @alpha -- experimental diff --git a/packages/grafana-runtime/src/types/analytics.ts b/packages/grafana-runtime/src/types/analytics.ts index 7a658d07d3f..6ec90387714 100644 --- a/packages/grafana-runtime/src/types/analytics.ts +++ b/packages/grafana-runtime/src/types/analytics.ts @@ -55,6 +55,8 @@ export interface DashboardViewEventPayload extends DashboardInfo { */ export interface DataRequestEventPayload extends DataRequestInfo { eventName: MetaAnalyticsEventName.DataRequest; + totalQueries?: number; + cachedQueries?: number; } /** diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts index 622dcaf4f8d..0a3b68801f7 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts @@ -1,5 +1,6 @@ +import { of } from 'rxjs'; import { BackendSrv, BackendSrvRequest } from 'src/services'; -import { DataSourceWithBackend, standardStreamOptionsProvider, toStreamingDataResponse } from './DataSourceWithBackend'; + import { DataSourceJsonData, DataQuery, @@ -9,7 +10,8 @@ import { MutableDataFrame, DataSourceRef, } from '@grafana/data'; -import { of } from 'rxjs'; + +import { DataSourceWithBackend, standardStreamOptionsProvider, toStreamingDataResponse } from './DataSourceWithBackend'; class MyDataSource extends DataSourceWithBackend { constructor(instanceSettings: DataSourceInstanceSettings) { diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index 9040bd70796..f79f76eaddb 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -1,3 +1,6 @@ +import { merge, Observable, of } from 'rxjs'; +import { catchError, switchMap } from 'rxjs/operators'; + import { DataSourceApi, DataQueryRequest, @@ -13,8 +16,8 @@ import { DataSourceRef, dataFrameToJSON, } from '@grafana/data'; -import { merge, Observable, of } from 'rxjs'; -import { catchError, switchMap } from 'rxjs/operators'; + +import { config } from '../config'; import { getBackendSrv, getDataSourceSrv, @@ -22,7 +25,7 @@ import { StreamingFrameOptions, StreamingFrameAction, } from '../services'; -import { config } from '../config'; + import { BackendDataSourceResponse, toDataQueryResponse } from './queryResponse'; /** diff --git a/packages/grafana-runtime/src/utils/analytics.ts b/packages/grafana-runtime/src/utils/analytics.ts index 3488de8eaf2..9ec3ce4b97d 100644 --- a/packages/grafana-runtime/src/utils/analytics.ts +++ b/packages/grafana-runtime/src/utils/analytics.ts @@ -1,3 +1,5 @@ +import { config } from '../config'; +import { locationService } from '../services'; import { getEchoSrv, EchoEventType } from '../services/EchoSrv'; import { ExperimentViewEchoEvent, @@ -6,8 +8,6 @@ import { MetaAnalyticsEventPayload, PageviewEchoEvent, } from '../types/analytics'; -import { locationService } from '../services'; -import { config } from '../config'; /** * Helper function to report meta analytics to the {@link EchoSrv}. diff --git a/packages/grafana-runtime/src/utils/plugin.ts b/packages/grafana-runtime/src/utils/plugin.ts index 5791dd00519..9a5363e3f07 100644 --- a/packages/grafana-runtime/src/utils/plugin.ts +++ b/packages/grafana-runtime/src/utils/plugin.ts @@ -1,7 +1,9 @@ +// @ts-ignore +import System from 'systemjs/dist/system.js'; + import { config } from '../config'; // @ts-ignore -import System from 'systemjs/dist/system.js'; /** * Option to specify a plugin css that should be applied for the dark diff --git a/packages/grafana-runtime/src/utils/queryResponse.test.ts b/packages/grafana-runtime/src/utils/queryResponse.test.ts index 36bdcdf953e..149c88526ac 100644 --- a/packages/grafana-runtime/src/utils/queryResponse.test.ts +++ b/packages/grafana-runtime/src/utils/queryResponse.test.ts @@ -1,5 +1,7 @@ -import { DataQuery, toDataFrameDTO, DataFrame } from '@grafana/data'; import { FetchError, FetchResponse } from 'src/services'; + +import { DataQuery, toDataFrameDTO, DataFrame } from '@grafana/data'; + import { BackendDataSourceResponse, cachedResponseNotice, toDataQueryResponse, toTestingStatus } from './queryResponse'; const resp = { @@ -298,10 +300,12 @@ describe('Query Response parser', () => { }; }); - test('adds notice for responses with X-Cache: HIT header', () => { + test('adds notice and cached boolean for responses with X-Cache: HIT header', () => { const queries: DataQuery[] = [{ refId: 'A' }]; resp.headers.set('X-Cache', 'HIT'); - expect(toDataQueryResponse(resp, queries).data[0].meta.notices).toStrictEqual([cachedResponseNotice]); + const meta = toDataQueryResponse(resp, queries).data[0].meta; + expect(meta.notices).toStrictEqual([cachedResponseNotice]); + expect(meta.isCachedResponse).toBeTruthy(); }); test('does not remove existing notices', () => { @@ -314,10 +318,11 @@ describe('Query Response parser', () => { ]); }); - test('does not add notice for responses with X-Cache: MISS header', () => { + test('does not add notice or cached response boolean for responses with X-Cache: MISS header', () => { const queries: DataQuery[] = [{ refId: 'A' }]; resp.headers.set('X-Cache', 'MISS'); expect(toDataQueryResponse(resp, queries).data[0].meta?.notices).toBeUndefined(); + expect(toDataQueryResponse(resp, queries).data[0].meta?.isCachedResponse).toBeUndefined(); }); test('does not add notice for responses without X-Cache header', () => { diff --git a/packages/grafana-runtime/src/utils/queryResponse.ts b/packages/grafana-runtime/src/utils/queryResponse.ts index 748efa4d57d..a17aaf1c1d9 100644 --- a/packages/grafana-runtime/src/utils/queryResponse.ts +++ b/packages/grafana-runtime/src/utils/queryResponse.ts @@ -14,7 +14,9 @@ import { dataFrameFromJSON, QueryResultMetaNotice, } from '@grafana/data'; + import { FetchError, FetchResponse } from '../services'; + import { toDataQueryError } from './toDataQueryError'; export const cachedResponseNotice: QueryResultMetaNotice = { severity: 'info', text: 'Cached response' }; @@ -152,6 +154,7 @@ function addCacheNotice(frame: DataFrameJSON): DataFrameJSON { meta: { ...frame.schema?.meta, notices: [...(frame.schema?.meta?.notices ?? []), cachedResponseNotice], + isCachedResponse: true, }, }, }; diff --git a/packages/grafana-schema/rollup.config.ts b/packages/grafana-schema/rollup.config.ts index 5342797fe32..f0159430417 100644 --- a/packages/grafana-schema/rollup.config.ts +++ b/packages/grafana-schema/rollup.config.ts @@ -1,5 +1,5 @@ -import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; import sourceMaps from 'rollup-plugin-sourcemaps'; import { terser } from 'rollup-plugin-terser'; diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci-alpine/install/bin/githubRelease.js b/packages/grafana-toolkit/docker/grafana-plugin-ci-alpine/install/bin/githubRelease.js index 54062c15767..5db7d4bcc2c 100644 --- a/packages/grafana-toolkit/docker/grafana-plugin-ci-alpine/install/bin/githubRelease.js +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci-alpine/install/bin/githubRelease.js @@ -1,11 +1,12 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +var fs = require('fs'); +var path = require('path'); var tslib_1 = require('tslib'); + var getPluginId_1 = require('../../config/utils/getPluginId'); var pluginValidation_1 = require('../../config/utils/pluginValidation'); var env_1 = require('../../plugins/env'); -var path = require('path'); -var fs = require('fs'); // @ts-ignore // import execa = require('execa'); var githubClient_1 = tslib_1.__importDefault(require('./githubClient')); diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index 467a23cf417..276107db1f7 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -49,7 +49,7 @@ "@types/webpack": "4.41.7", "@typescript-eslint/eslint-plugin": "4.28.0", "@typescript-eslint/parser": "4.28.0", - "axios": "0.21.2", + "axios": "0.26.1", "babel-jest": "26.6.3", "babel-loader": "8.2.2", "babel-plugin-angularjs-annotate": "0.10.0", diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index f8ec7bc945d..8dc814cf0fb 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -1,26 +1,27 @@ // @ts-ignore import chalk from 'chalk'; import { program } from 'commander'; -import { execTask } from './utils/execTask'; -import { startTask } from './tasks/core.start'; + import { changelogTask } from './tasks/changelog'; import { cherryPickTask } from './tasks/cherrypick'; -import { templateTask } from './tasks/template'; -import { pluginBuildTask } from './tasks/plugin.build'; -import { toolkitBuildTask } from './tasks/toolkit.build'; -import { pluginTestTask } from './tasks/plugin.tests'; -import { searchTestDataSetupTask } from './tasks/searchTestDataSetup'; import { closeMilestoneTask } from './tasks/closeMilestone'; -import { pluginDevTask } from './tasks/plugin.dev'; -import { getToolkitVersion, githubPublishTask } from './tasks/plugin.utils'; -import { pluginUpdateTask } from './tasks/plugin.update'; -import { ciBuildPluginTask, ciPackagePluginTask, ciPluginReportTask } from './tasks/plugin.ci'; -import { buildPackageTask } from './tasks/package.build'; -import { pluginCreateTask } from './tasks/plugin.create'; -import { pluginSignTask } from './tasks/plugin.sign'; -import { bundleManagedTask } from './tasks/plugin/bundle.managed'; import { componentCreateTask } from './tasks/component.create'; +import { startTask } from './tasks/core.start'; import { nodeVersionCheckerTask } from './tasks/nodeVersionChecker'; +import { buildPackageTask } from './tasks/package.build'; +import { pluginBuildTask } from './tasks/plugin.build'; +import { ciBuildPluginTask, ciPackagePluginTask, ciPluginReportTask } from './tasks/plugin.ci'; +import { pluginCreateTask } from './tasks/plugin.create'; +import { pluginDevTask } from './tasks/plugin.dev'; +import { pluginSignTask } from './tasks/plugin.sign'; +import { pluginTestTask } from './tasks/plugin.tests'; +import { pluginUpdateTask } from './tasks/plugin.update'; +import { getToolkitVersion, githubPublishTask } from './tasks/plugin.utils'; +import { bundleManagedTask } from './tasks/plugin/bundle.managed'; +import { searchTestDataSetupTask } from './tasks/searchTestDataSetup'; +import { templateTask } from './tasks/template'; +import { toolkitBuildTask } from './tasks/toolkit.build'; +import { execTask } from './utils/execTask'; export const run = (includeInternalScripts = false) => { if (includeInternalScripts) { diff --git a/packages/grafana-toolkit/src/cli/tasks/changelog.ts b/packages/grafana-toolkit/src/cli/tasks/changelog.ts index 7e797d15ec9..9815045be4b 100644 --- a/packages/grafana-toolkit/src/cli/tasks/changelog.ts +++ b/packages/grafana-toolkit/src/cli/tasks/changelog.ts @@ -1,9 +1,11 @@ -import { difference, sortBy } from 'lodash'; -import { Task } from './task'; -import GithubClient from '../utils/githubClient'; import chalk from 'chalk'; +import { difference, sortBy } from 'lodash'; + +import GithubClient from '../utils/githubClient'; import { useSpinner } from '../utils/useSpinner'; +import { Task } from './task'; + interface ChangelogOptions { milestone: string; } diff --git a/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts b/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts index 1a446a13381..3aa66dd33f2 100644 --- a/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts +++ b/packages/grafana-toolkit/src/cli/tasks/cherrypick.ts @@ -1,6 +1,7 @@ -import { Task, TaskRunner } from './task'; import GithubClient from '../utils/githubClient'; +import { Task, TaskRunner } from './task'; + interface CherryPickOptions { enterprise: boolean; } diff --git a/packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts b/packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts index 0ac396a36fe..39a67830510 100644 --- a/packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts +++ b/packages/grafana-toolkit/src/cli/tasks/closeMilestone.ts @@ -1,6 +1,7 @@ -import { Task, TaskRunner } from './task'; import GithubClient from '../utils/githubClient'; +import { Task, TaskRunner } from './task'; + interface CloseMilestoneOptions { milestone: string; dryRun: boolean; diff --git a/packages/grafana-toolkit/src/cli/tasks/component.create.ts b/packages/grafana-toolkit/src/cli/tasks/component.create.ts index 8227c450c36..311c4223a82 100644 --- a/packages/grafana-toolkit/src/cli/tasks/component.create.ts +++ b/packages/grafana-toolkit/src/cli/tasks/component.create.ts @@ -1,10 +1,12 @@ -import { Task, TaskRunner } from './task'; import fs from 'fs'; -import { template as _template } from 'lodash'; import { prompt } from 'inquirer'; +import { template as _template } from 'lodash'; + +import { componentTpl, docsTpl, storyTpl, testTpl } from '../templates'; import { pascalCase } from '../utils/pascalCase'; import { promptConfirm, promptInput, promptList } from '../utils/prompt'; -import { componentTpl, docsTpl, storyTpl, testTpl } from '../templates'; + +import { Task, TaskRunner } from './task'; interface Details { name?: string; diff --git a/packages/grafana-toolkit/src/cli/tasks/core.start.ts b/packages/grafana-toolkit/src/cli/tasks/core.start.ts index d3d94dbc9f4..0d7e372f05f 100644 --- a/packages/grafana-toolkit/src/cli/tasks/core.start.ts +++ b/packages/grafana-toolkit/src/cli/tasks/core.start.ts @@ -1,5 +1,6 @@ //@ts-ignore import concurrently from 'concurrently'; + import { Task, TaskRunner } from './task'; interface StartTaskOptions { diff --git a/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts b/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts index 3e9942b08b5..d7bcbe0ff8e 100644 --- a/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts +++ b/packages/grafana-toolkit/src/cli/tasks/nodeVersionChecker.ts @@ -1,7 +1,8 @@ -import { Task, TaskRunner } from './task'; import chalk from 'chalk'; -import { coerce, satisfies } from 'semver'; import { readFileSync } from 'fs'; +import { coerce, satisfies } from 'semver'; + +import { Task, TaskRunner } from './task'; interface FailedVersionCheck { file: string; diff --git a/packages/grafana-toolkit/src/cli/tasks/package.build.ts b/packages/grafana-toolkit/src/cli/tasks/package.build.ts index 5e00ffb2f47..844cc76e798 100644 --- a/packages/grafana-toolkit/src/cli/tasks/package.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/package.build.ts @@ -1,11 +1,13 @@ +import chalk from 'chalk'; import execa = require('execa'); import { promises as fs } from 'fs'; -import * as path from 'path'; -import chalk from 'chalk'; -import { useSpinner } from '../utils/useSpinner'; -import { Task, TaskRunner } from './task'; -import { cloneDeep } from 'lodash'; import globby from 'globby'; +import { cloneDeep } from 'lodash'; +import * as path from 'path'; + +import { useSpinner } from '../utils/useSpinner'; + +import { Task, TaskRunner } from './task'; const clean = (cwd: string) => useSpinner('Cleaning', () => execa('npm', ['run', 'clean'], { cwd })); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts index 02b6d668477..6fc22e1de95 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts @@ -1,14 +1,16 @@ -import { useSpinner } from '../utils/useSpinner'; -import { testPlugin } from './plugin/tests'; -import { Task, TaskRunner } from './task'; -import rimrafCallback from 'rimraf'; -import { resolve as resolvePath } from 'path'; -import { promisify } from 'util'; -import globby from 'globby'; +import { CLIEngine } from 'eslint'; import execa from 'execa'; import { constants as fsConstants, promises as fs } from 'fs'; -import { CLIEngine } from 'eslint'; +import globby from 'globby'; +import { resolve as resolvePath } from 'path'; +import rimrafCallback from 'rimraf'; +import { promisify } from 'util'; + +import { useSpinner } from '../utils/useSpinner'; + import { bundlePlugin as bundleFn, PluginBundleOptions } from './plugin/bundle'; +import { testPlugin } from './plugin/tests'; +import { Task, TaskRunner } from './task'; const { access, copyFile } = fs; const { COPYFILE_EXCL } = fsConstants; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts index eb880cd5c96..40d8eb0072f 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts @@ -1,12 +1,11 @@ -import { Task, TaskRunner } from './task'; -import { pluginBuildRunner } from './plugin.build'; -import { getPluginJson } from '../../config/utils/pluginValidation'; -import { getPluginId } from '../../config/utils/getPluginId'; import execa = require('execa'); -import path = require('path'); import fs from 'fs-extra'; -import { getPackageDetails, getGrafanaVersions, readGitLog } from '../../plugins/utils'; -import { buildManifest, signManifest, saveManifest } from '../../plugins/manifest'; +import path = require('path'); +import rimrafCallback from 'rimraf'; +import { promisify } from 'util'; + +import { getPluginId } from '../../config/utils/getPluginId'; +import { getPluginJson } from '../../config/utils/pluginValidation'; import { getJobFolder, writeJobStats, @@ -15,10 +14,13 @@ import { getPullRequestNumber, getCircleDownloadBaseURL, } from '../../plugins/env'; -import { agregateWorkflowInfo, agregateCoverageInfo, agregateTestInfo } from '../../plugins/workflow'; +import { buildManifest, signManifest, saveManifest } from '../../plugins/manifest'; import { PluginPackageDetails, PluginBuildReport } from '../../plugins/types'; -import rimrafCallback from 'rimraf'; -import { promisify } from 'util'; +import { getPackageDetails, getGrafanaVersions, readGitLog } from '../../plugins/utils'; +import { agregateWorkflowInfo, agregateCoverageInfo, agregateTestInfo } from '../../plugins/workflow'; + +import { pluginBuildRunner } from './plugin.build'; +import { Task, TaskRunner } from './task'; const rimraf = promisify(rimrafCallback); export interface PluginCIOptions { diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.create.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.create.ts index 2b9a400df71..b276b869716 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.create.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.create.ts @@ -1,6 +1,8 @@ import { prompt } from 'inquirer'; import path from 'path'; + import { promptConfirm } from '../utils/prompt'; + import { fetchTemplate, formatPluginDetails, diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts index 7585bdf5956..1b9a15b4136 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.dev.ts @@ -1,10 +1,12 @@ -import { Task, TaskRunner } from './task'; -import { bundlePlugin as bundleFn, PluginBundleOptions } from './plugin/bundle'; -import { useSpinner } from '../utils/useSpinner'; -import { lintPlugin } from './plugin.build'; import execa = require('execa'); import path = require('path'); +import { useSpinner } from '../utils/useSpinner'; + +import { lintPlugin } from './plugin.build'; +import { bundlePlugin as bundleFn, PluginBundleOptions } from './plugin/bundle'; +import { Task, TaskRunner } from './task'; + const bundlePlugin = (options: PluginBundleOptions) => useSpinner('Bundling plugin in dev mode', () => bundleFn(options)); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.sign.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.sign.ts index c8c566a88b6..7424bdeb1fb 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.sign.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.sign.ts @@ -1,7 +1,9 @@ import path from 'path'; + import { buildManifest, signManifest, saveManifest } from '../../plugins/manifest'; -import { Task, TaskRunner } from './task'; + import { getToolkitVersion } from './plugin.utils'; +import { Task, TaskRunner } from './task'; interface PluginSignOptions { signatureType?: string; rootUrls?: string[]; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts index f85c3f18828..ff75d91d1d6 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.tests.ts @@ -1,5 +1,5 @@ -import { Task, TaskRunner } from './task'; import { testPlugin, PluginTestOptions } from './plugin/tests'; +import { Task, TaskRunner } from './task'; const pluginTestRunner: TaskRunner = async (options) => { await testPlugin(options); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.update.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.update.ts index 026d9d05dcf..fee3286b395 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.update.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.update.ts @@ -1,8 +1,10 @@ -import { Task, TaskRunner } from './task'; -import { useSpinner } from '../utils/useSpinner'; import fs = require('fs'); import path = require('path'); +import { useSpinner } from '../utils/useSpinner'; + +import { Task, TaskRunner } from './task'; + interface UpdatePluginTask {} const updateCiConfig = () => diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts index ee32cf54177..7852bd1d8b6 100755 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts @@ -1,13 +1,14 @@ -import { Task, TaskRunner } from './task'; -import { getPluginJson } from '../../config/utils/pluginValidation'; -import { GitHubRelease } from '../utils/githubRelease'; -import { getPluginId } from '../../config/utils/getPluginId'; -import { getCiFolder } from '../../plugins/env'; -import { useSpinner } from '../utils/useSpinner'; -import { readFileSync } from 'fs'; - -import path = require('path'); import execa = require('execa'); +import { readFileSync } from 'fs'; +import path = require('path'); + +import { getPluginId } from '../../config/utils/getPluginId'; +import { getPluginJson } from '../../config/utils/pluginValidation'; +import { getCiFolder } from '../../plugins/env'; +import { GitHubRelease } from '../utils/githubRelease'; +import { useSpinner } from '../utils/useSpinner'; + +import { Task, TaskRunner } from './task'; interface Command extends Array {} const DEFAULT_EMAIL_ADDRESS = 'eng@grafana.com'; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts index 052c067a759..7ffd6c32d6d 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.managed.ts @@ -1,6 +1,7 @@ -import { promises as fs } from 'fs'; -import { Task, TaskRunner } from '../task'; import execa = require('execa'); +import { promises as fs } from 'fs'; + +import { Task, TaskRunner } from '../task'; interface BundeManagedOptions {} diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts index 003dcf82786..345392ad632 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts @@ -1,6 +1,7 @@ -import webpack = require('webpack'); -import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); import clearConsole = require('react-dev-utils/clearConsole'); +import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +import webpack = require('webpack'); + import { loadWebpackConfig } from '../../../config/webpack.plugin.config'; export interface PluginBundleOptions { diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/create.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/create.ts index f4f9143944c..5073acadfe9 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin/create.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/create.ts @@ -5,6 +5,7 @@ import { prompt } from 'inquirer'; import { kebabCase } from 'lodash'; import path from 'path'; import gitPromise from 'simple-git'; + import { promptConfirm, promptInput } from '../../utils/prompt'; import { rmdir } from '../../utils/rmdir'; import { useSpinner } from '../../utils/useSpinner'; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts index dd47ba0938c..7b8970bf769 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts @@ -1,6 +1,7 @@ import { runCLI } from '@jest/core'; -import { useSpinner } from '../../utils/useSpinner'; + import { loadJestPluginConfig } from '../../../config/jest.plugin.config'; +import { useSpinner } from '../../utils/useSpinner'; export interface PluginTestOptions { updateSnapshot: boolean; diff --git a/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts b/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts index 84c0ac0cc82..6f8fc346e6d 100644 --- a/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts +++ b/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts @@ -1,4 +1,5 @@ import axios from 'axios'; + import { Task, TaskRunner } from './task'; interface SearchTestDataSetupOptions { diff --git a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts index 47ccccd0e2f..ae1308d9a17 100644 --- a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts @@ -1,7 +1,9 @@ +import chalk from 'chalk'; import execa = require('execa'); import * as fs from 'fs'; -import chalk from 'chalk'; + import { useSpinner } from '../utils/useSpinner'; + import { Task, TaskRunner } from './task'; const path = require('path'); diff --git a/packages/grafana-toolkit/src/cli/utils/execTask.ts b/packages/grafana-toolkit/src/cli/utils/execTask.ts index a4fe883169f..906fcc6ed07 100644 --- a/packages/grafana-toolkit/src/cli/utils/execTask.ts +++ b/packages/grafana-toolkit/src/cli/utils/execTask.ts @@ -1,7 +1,8 @@ /* eslint-disable no-console */ -import { Task } from '../tasks/task'; import chalk from 'chalk'; +import { Task } from '../tasks/task'; + interface TaskBasicOptions { // Don't print task details when running silent?: boolean; diff --git a/packages/grafana-toolkit/src/cli/utils/githubRelease.ts b/packages/grafana-toolkit/src/cli/utils/githubRelease.ts index 2dbd1b6126e..1d59e2d4516 100644 --- a/packages/grafana-toolkit/src/cli/utils/githubRelease.ts +++ b/packages/grafana-toolkit/src/cli/utils/githubRelease.ts @@ -1,10 +1,12 @@ +import { AxiosResponse } from 'axios'; +import fs = require('fs'); +import path = require('path'); + import { getPluginId } from '../../config/utils/getPluginId'; import { getPluginJson } from '../../config/utils/pluginValidation'; import { getCiFolder } from '../../plugins/env'; -import path = require('path'); -import fs = require('fs'); + import GithubClient from './githubClient'; -import { AxiosResponse } from 'axios'; const resolveContentType = (extension: string): string => { if (extension.startsWith('.')) { diff --git a/packages/grafana-toolkit/src/config/jest.plugin.config.ts b/packages/grafana-toolkit/src/config/jest.plugin.config.ts index c0539c4d0b5..647dc976526 100644 --- a/packages/grafana-toolkit/src/config/jest.plugin.config.ts +++ b/packages/grafana-toolkit/src/config/jest.plugin.config.ts @@ -1,5 +1,5 @@ -import path = require('path'); import fs from 'fs'; +import path = require('path'); export const allowedJestConfigOverrides = [ 'snapshotSerializers', diff --git a/packages/grafana-toolkit/src/config/webpack.plugin.config.test.ts b/packages/grafana-toolkit/src/config/webpack.plugin.config.test.ts index 7d893834c56..ea20c19979f 100644 --- a/packages/grafana-toolkit/src/config/webpack.plugin.config.test.ts +++ b/packages/grafana-toolkit/src/config/webpack.plugin.config.test.ts @@ -1,5 +1,6 @@ -import { findModuleFiles, loadWebpackConfig } from './webpack.plugin.config'; import fs from 'fs'; + +import { findModuleFiles, loadWebpackConfig } from './webpack.plugin.config'; // eslint-disable-next-line no-duplicate-imports import * as webpackConfig from './webpack.plugin.config'; diff --git a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts index 60ccf8f3c81..09f57379e9b 100644 --- a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts +++ b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts @@ -1,20 +1,21 @@ -const fs = require('fs'); -const util = require('util'); -const path = require('path'); +import * as webpack from 'webpack'; + +import { getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders'; + const CopyWebpackPlugin = require('copy-webpack-plugin'); -const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const fs = require('fs'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const path = require('path'); +const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const util = require('util'); const readdirPromise = util.promisify(fs.readdir); const accessPromise = util.promisify(fs.access); -import * as webpack from 'webpack'; -import { getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders'; - export interface WebpackConfigurationOptions { watch?: boolean; production?: boolean; diff --git a/packages/grafana-toolkit/src/config/webpack/loaders.ts b/packages/grafana-toolkit/src/config/webpack/loaders.ts index b247726ee75..fc4a386df45 100644 --- a/packages/grafana-toolkit/src/config/webpack/loaders.ts +++ b/packages/grafana-toolkit/src/config/webpack/loaders.ts @@ -1,5 +1,6 @@ import fs from 'fs'; import path from 'path'; + import { getPluginId } from '../utils/getPluginId'; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); diff --git a/packages/grafana-toolkit/src/plugins/env.ts b/packages/grafana-toolkit/src/plugins/env.ts index 06b659c1c3f..f79b68d449d 100644 --- a/packages/grafana-toolkit/src/plugins/env.ts +++ b/packages/grafana-toolkit/src/plugins/env.ts @@ -1,7 +1,9 @@ import execa from 'execa'; -import path from 'path'; import fs from 'fs'; +import path from 'path'; + import { PluginBuildInfo } from '@grafana/data'; + import { JobInfo } from './types'; const getJobFromProcessArgv = () => { diff --git a/packages/grafana-toolkit/src/plugins/manifest.ts b/packages/grafana-toolkit/src/plugins/manifest.ts index 1b209c6f3d9..17c1f052170 100644 --- a/packages/grafana-toolkit/src/plugins/manifest.ts +++ b/packages/grafana-toolkit/src/plugins/manifest.ts @@ -1,6 +1,7 @@ -import path from 'path'; -import fs from 'fs'; import crypto from 'crypto'; +import fs from 'fs'; +import path from 'path'; + import { ManifestInfo } from './types'; const MANIFEST_FILE = 'MANIFEST.txt'; diff --git a/packages/grafana-toolkit/src/plugins/utils.ts b/packages/grafana-toolkit/src/plugins/utils.ts index 739c837b1e8..e968fc31fd0 100644 --- a/packages/grafana-toolkit/src/plugins/utils.ts +++ b/packages/grafana-toolkit/src/plugins/utils.ts @@ -1,7 +1,9 @@ import execa from 'execa'; -import path from 'path'; import fs from 'fs'; +import path from 'path'; + import { KeyValue } from '@grafana/data'; + import { ExtensionSize, ZipFileInfo, GitLogInfo } from './types'; const md5File = require('md5-file'); diff --git a/packages/grafana-toolkit/src/plugins/workflow.ts b/packages/grafana-toolkit/src/plugins/workflow.ts index 770afe48a18..ea653836841 100644 --- a/packages/grafana-toolkit/src/plugins/workflow.ts +++ b/packages/grafana-toolkit/src/plugins/workflow.ts @@ -1,7 +1,8 @@ -import path from 'path'; import fs from 'fs'; -import { JobInfo, WorkflowInfo, CoverageInfo, TestResultsInfo } from './types'; +import path from 'path'; + import { getBuildNumber, getCiFolder } from './env'; +import { JobInfo, WorkflowInfo, CoverageInfo, TestResultsInfo } from './types'; export const agregateWorkflowInfo = (): WorkflowInfo => { const now = Date.now(); diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index d73b877eb70..9f550da54f2 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -118,7 +118,7 @@ "@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.4", "@testing-library/react-hooks": "7.0.2", - "@testing-library/user-event": "13.5.0", + "@testing-library/user-event": "14.1.1", "@types/classnames": "2.3.0", "@types/common-tags": "^1.8.0", "@types/d3": "7.1.0", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index 71f712df14f..a19e76cb0eb 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -1,5 +1,5 @@ -import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; import svg from 'rollup-plugin-svg-import'; import { terser } from 'rollup-plugin-terser'; diff --git a/packages/grafana-ui/src/components/Alert/Alert.story.tsx b/packages/grafana-ui/src/components/Alert/Alert.story.tsx index cb159b1862e..ac1cd03c2b6 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.story.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.story.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import { Story } from '@storybook/react'; import { action } from '@storybook/addon-actions'; +import { Story } from '@storybook/react'; +import React from 'react'; + import { Alert, AlertVariant, VerticalGroup } from '@grafana/ui'; -import { Props } from './Alert'; + +import { StoryExample } from '../../utils/storybook/StoryExample'; import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; import mdx from '../Alert/Alert.mdx'; -import { StoryExample } from '../../utils/storybook/StoryExample'; + +import { Props } from './Alert'; const severities: AlertVariant[] = ['error', 'warning', 'info', 'success']; diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index c6e73b7fe2b..5acb3049b10 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -1,12 +1,14 @@ -import React, { HTMLAttributes, ReactNode } from 'react'; import { css, cx } from '@emotion/css'; +import React, { HTMLAttributes, ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; + import { useTheme2 } from '../../themes'; -import { Icon } from '../Icon/Icon'; import { IconName } from '../../types/icon'; -import { IconButton } from '../IconButton/IconButton'; import { Button } from '../Button/Button'; +import { Icon } from '../Icon/Icon'; +import { IconButton } from '../IconButton/IconButton'; export type AlertVariant = 'success' | 'warning' | 'error' | 'info'; @@ -22,7 +24,7 @@ export interface Props extends HTMLAttributes { topSpacing?: number; } -function getIconFromSeverity(severity: AlertVariant): string { +export function getIconFromSeverity(severity: AlertVariant): string { switch (severity) { case 'error': case 'warning': @@ -150,7 +152,7 @@ const getStyles = ( color: ${theme.colors.text.secondary}; padding-top: ${theme.spacing(1)}; max-height: 50vh; - overflow-y: scroll; + overflow-y: auto; `, buttonWrapper: css` padding: ${theme.spacing(1)}; diff --git a/packages/grafana-ui/src/components/Badge/Badge.story.tsx b/packages/grafana-ui/src/components/Badge/Badge.story.tsx index 32aa3a63969..7bf2c11bbcb 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.story.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.story.tsx @@ -1,8 +1,11 @@ -import React from 'react'; import { Story } from '@storybook/react'; +import React from 'react'; + import { Badge, BadgeProps } from '@grafana/ui'; + import { iconOptions } from '../../utils/storybook/knobs'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './Badge.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx index 053f063e51d..aa81bdfed1b 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -1,13 +1,15 @@ +import { css, cx } from '@emotion/css'; import React, { HTMLAttributes } from 'react'; -import { Icon } from '../Icon/Icon'; +import tinycolor from 'tinycolor2'; + +import { getColorForTheme, GrafanaTheme } from '@grafana/data'; + import { useTheme } from '../../themes/ThemeContext'; import { stylesFactory } from '../../themes/stylesFactory'; import { IconName } from '../../types'; -import { Tooltip } from '../Tooltip/Tooltip'; -import { getColorForTheme, GrafanaTheme } from '@grafana/data'; -import tinycolor from 'tinycolor2'; -import { css, cx } from '@emotion/css'; +import { Icon } from '../Icon/Icon'; import { HorizontalGroup } from '../Layout/Layout'; +import { Tooltip } from '../Tooltip/Tooltip'; export type BadgeColor = 'blue' | 'red' | 'green' | 'orange' | 'purple'; diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx index 232ff7d67f7..6857739711b 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react'; -import { BarGauge, BarGaugeDisplayMode } from '@grafana/ui'; +import React from 'react'; + import { VizOrientation, ThresholdsMode, Field, FieldType, getDisplayProcessor } from '@grafana/data'; -import { Props } from './BarGauge'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import mdx from './BarGauge.mdx'; +import { BarGauge, BarGaugeDisplayMode } from '@grafana/ui'; + import { useTheme2 } from '../../themes'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { Props } from './BarGauge'; +import mdx from './BarGauge.mdx'; export default { title: 'Visualizations/BarGauge', diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx index e92a4af9dc3..ecafd8ffeae 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { DisplayValue, VizOrientation, @@ -10,6 +11,7 @@ import { getDisplayProcessor, createTheme, } from '@grafana/data'; + import { BarGauge, Props, diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index b83267f1e05..6155bacad5a 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -1,6 +1,7 @@ // Library import React, { CSSProperties, PureComponent, ReactNode } from 'react'; import tinycolor from 'tinycolor2'; + import { DisplayProcessor, DisplayValue, @@ -19,9 +20,10 @@ import { VizOrientation, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay'; -import { calculateFontSize, measureText } from '../../utils/measureText'; + import { Themeable2 } from '../../types'; +import { calculateFontSize, measureText } from '../../utils/measureText'; +import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay'; const MIN_VALUE_HEIGHT = 18; const MAX_VALUE_HEIGHT = 50; diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx index 7b1ee1b665b..4376f990360 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx @@ -1,5 +1,11 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react'; +import React from 'react'; + +import { ArrayVector, FieldSparkline, FieldType } from '@grafana/data'; + +import { useTheme2 } from '../../themes'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { BigValue, BigValueColorMode, @@ -8,10 +14,7 @@ import { BigValueTextMode, Props, } from './BigValue'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import mdx from './BigValue.mdx'; -import { useTheme2 } from '../../themes'; -import { ArrayVector, FieldSparkline, FieldType } from '@grafana/data'; export default { title: 'Visualizations/BigValue', diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx index 16efc2b43c3..645d40a1e48 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { BigValue, BigValueColorMode, BigValueGraphMode, Props } from './BigValue'; +import React from 'react'; + import { createTheme } from '@grafana/data'; +import { BigValue, BigValueColorMode, BigValueGraphMode, Props } from './BigValue'; + function getProps(propOverrides?: Partial): Props { const props: Props = { colorMode: BigValueColorMode.Background, diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx index 864c2547199..789bd7fd93a 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -1,10 +1,12 @@ import React, { PureComponent } from 'react'; + import { DisplayValue, DisplayValueAlignmentFactors, FieldSparkline, TextDisplayOptions } from '@grafana/data'; import { Themeable2 } from '../../types'; -import { buildLayout } from './BigValueLayout'; import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay'; +import { buildLayout } from './BigValueLayout'; + export enum BigValueColorMode { Value = 'value', Background = 'background', diff --git a/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx b/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx index 7b7d2b6791c..373b93e0b94 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValueLayout.test.tsx @@ -1,6 +1,7 @@ +import { ArrayVector, createTheme, FieldType } from '@grafana/data'; + import { Props, BigValueColorMode, BigValueGraphMode, BigValueTextMode } from './BigValue'; import { buildLayout, StackedWithChartLayout, StackedWithNoChartLayout, WideWithChartLayout } from './BigValueLayout'; -import { ArrayVector, createTheme, FieldType } from '@grafana/data'; function getProps(propOverrides?: Partial): Props { const props: Props = { diff --git a/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx b/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx index 7f3bde9344f..d7c04e01fdd 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx @@ -1,17 +1,15 @@ -// Libraries import React, { CSSProperties } from 'react'; import tinycolor from 'tinycolor2'; -// Utils import { formattedValueToString, DisplayValue, FieldConfig, FieldType } from '@grafana/data'; -import { calculateFontSize } from '../../utils/measureText'; - -// Types -import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue'; -import { getTextColorForBackground } from '../../utils'; import { GraphDrawStyle, GraphFieldConfig } from '@grafana/schema'; + +import { getTextColorForBackground } from '../../utils'; +import { calculateFontSize } from '../../utils/measureText'; import { Sparkline } from '../Sparkline/Sparkline'; +import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue'; + const LINE_HEIGHT = 1.2; const MAX_TITLE_SIZE = 30; diff --git a/packages/grafana-ui/src/components/BrowserLabel/Label.tsx b/packages/grafana-ui/src/components/BrowserLabel/Label.tsx index 8132c891b5a..fb4c77592d5 100644 --- a/packages/grafana-ui/src/components/BrowserLabel/Label.tsx +++ b/packages/grafana-ui/src/components/BrowserLabel/Label.tsx @@ -1,11 +1,13 @@ -import React, { forwardRef, HTMLAttributes, useCallback } from 'react'; import { cx, css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { useTheme2 } from '../../themes'; +import React, { forwardRef, HTMLAttributes, useCallback } from 'react'; // @ts-ignore import Highlighter from 'react-highlight-words'; -import { PartialHighlighter } from '../Typeahead/PartialHighlighter'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useTheme2 } from '../../themes'; import { HighlightPart } from '../../types'; +import { PartialHighlighter } from '../Typeahead/PartialHighlighter'; type OnLabelClick = (name: string, value: string | undefined, event: React.MouseEvent) => void; diff --git a/packages/grafana-ui/src/components/Button/Button.story.tsx b/packages/grafana-ui/src/components/Button/Button.story.tsx index 9d70bf1b304..66588655cbc 100644 --- a/packages/grafana-ui/src/components/Button/Button.story.tsx +++ b/packages/grafana-ui/src/components/Button/Button.story.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react'; -import { allButtonVariants, allButtonFills, Button, ButtonProps } from './Button'; -import mdx from './Button.mdx'; -import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; -import { ButtonGroup } from './ButtonGroup'; +import React from 'react'; + import { ComponentSize } from '../../types/size'; import { Card } from '../Card/Card'; +import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; + +import { allButtonVariants, allButtonFills, Button, ButtonProps } from './Button'; +import mdx from './Button.mdx'; +import { ButtonGroup } from './ButtonGroup'; export default { title: 'Buttons/Button', diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index d1cf2db8dd4..bfe0ad73563 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -1,11 +1,13 @@ -import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; import { css, CSSObject, cx } from '@emotion/css'; -import { useTheme2 } from '../../themes'; -import { IconName } from '../../types/icon'; -import { getPropertiesForButtonSize } from '../Forms/commonStyles'; +import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; + import { colorManipulator, GrafanaTheme2, ThemeRichColor } from '@grafana/data'; -import { ComponentSize } from '../../types/size'; + +import { useTheme2 } from '../../themes'; import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; +import { IconName } from '../../types/icon'; +import { ComponentSize } from '../../types/size'; +import { getPropertiesForButtonSize } from '../Forms/commonStyles'; import { Icon } from '../Icon/Icon'; export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link'; diff --git a/packages/grafana-ui/src/components/Button/ButtonGroup.tsx b/packages/grafana-ui/src/components/Button/ButtonGroup.tsx index 456bb7ab811..3fe6187198b 100644 --- a/packages/grafana-ui/src/components/Button/ButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Button/ButtonGroup.tsx @@ -1,8 +1,10 @@ -import React, { forwardRef, HTMLAttributes } from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '../../themes'; +import React, { forwardRef, HTMLAttributes } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '../../themes'; + export interface Props extends HTMLAttributes { className?: string; } diff --git a/packages/grafana-ui/src/components/Button/FullWidthButtonContainer.tsx b/packages/grafana-ui/src/components/Button/FullWidthButtonContainer.tsx index 601aec9ffa5..e49056997f1 100644 --- a/packages/grafana-ui/src/components/Button/FullWidthButtonContainer.tsx +++ b/packages/grafana-ui/src/components/Button/FullWidthButtonContainer.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; + import { stylesFactory } from '../../themes'; export interface Props { diff --git a/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx b/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx index d20f90cde72..f152001023c 100644 --- a/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx +++ b/packages/grafana-ui/src/components/Button/ToolbarButton.story.tsx @@ -1,9 +1,12 @@ import React from 'react'; + import { ToolbarButton, ButtonGroup, VerticalGroup, HorizontalGroup } from '@grafana/ui'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { ToolbarButtonRow } from './ToolbarButtonRow'; -import { ToolbarButtonVariant } from './ToolbarButton'; + import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { ToolbarButtonVariant } from './ToolbarButton'; +import { ToolbarButtonRow } from './ToolbarButtonRow'; export default { title: 'Buttons/ToolbarButton', diff --git a/packages/grafana-ui/src/components/Button/ToolbarButton.tsx b/packages/grafana-ui/src/components/Button/ToolbarButton.tsx index c262b2962eb..739c27e3fdf 100644 --- a/packages/grafana-ui/src/components/Button/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/Button/ToolbarButton.tsx @@ -1,14 +1,17 @@ -import React, { forwardRef, ButtonHTMLAttributes } from 'react'; import { cx, css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { styleMixins, useStyles2 } from '../../themes'; -import { IconName } from '../../types/icon'; -import { Tooltip } from '../Tooltip/Tooltip'; -import { Icon } from '../Icon/Icon'; -import { getPropertiesForVariant } from './Button'; import { isString } from 'lodash'; +import React, { forwardRef, ButtonHTMLAttributes } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; + +import { styleMixins, useStyles2 } from '../../themes'; import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; +import { IconName } from '../../types/icon'; +import { Icon } from '../Icon/Icon'; +import { Tooltip } from '../Tooltip/Tooltip'; + +import { getPropertiesForVariant } from './Button'; export interface Props extends ButtonHTMLAttributes { /** Icon name */ diff --git a/packages/grafana-ui/src/components/Button/ToolbarButtonRow.tsx b/packages/grafana-ui/src/components/Button/ToolbarButtonRow.tsx index eda19387c44..cfc2493a68e 100644 --- a/packages/grafana-ui/src/components/Button/ToolbarButtonRow.tsx +++ b/packages/grafana-ui/src/components/Button/ToolbarButtonRow.tsx @@ -1,8 +1,10 @@ -import React, { forwardRef, HTMLAttributes } from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '../../themes'; +import React, { forwardRef, HTMLAttributes } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '../../themes'; + export interface Props extends HTMLAttributes { className?: string; } diff --git a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx index 27b5e5cbfe7..c4970a83e33 100644 --- a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx +++ b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React from 'react'; + import { ButtonCascader } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { ButtonCascaderProps } from './ButtonCascader'; export default { diff --git a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx index a57f85d85ad..c0ad9212b9c 100644 --- a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx +++ b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx @@ -1,13 +1,14 @@ -import React from 'react'; -import { IconName } from '../../types/icon'; import { css } from '@emotion/css'; - import RCCascader from 'rc-cascader'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory, useTheme2 } from '../../themes'; +import { IconName } from '../../types/icon'; +import { Button, ButtonProps } from '../Button'; import { CascaderOption } from '../Cascader/Cascader'; import { onChangeCascader, onLoadDataCascader } from '../Cascader/optionMappings'; -import { stylesFactory, useTheme2 } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, ButtonProps } from '../Button'; import { Icon } from '../Icon/Icon'; export interface ButtonCascaderProps { diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx index 31ea9c22152..b048dfb9acf 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { CallToActionCard, CallToActionCardProps } from './CallToActionCard'; -import { Story, Meta } from '@storybook/react'; -import { Button } from '../Button/Button'; import { action } from '@storybook/addon-actions'; +import { Story, Meta } from '@storybook/react'; +import React from 'react'; + +import { Button } from '../Button/Button'; + +import { CallToActionCard, CallToActionCardProps } from './CallToActionCard'; export default { title: 'Layout/CallToActionCard', diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx index 1e2a72d8f81..4d7a66badd2 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { CallToActionCard } from './CallToActionCard'; +import React from 'react'; + import { expect } from '../../../../../public/test/lib/common'; +import { CallToActionCard } from './CallToActionCard'; + describe('CallToActionCard', () => { describe('rendering', () => { it('should render callToActionElement', () => { diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx index d566a866d79..64b179aeece 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '../../themes/ThemeContext'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '../../themes/ThemeContext'; + export interface CallToActionCardProps { message?: string | JSX.Element; callToActionElement: JSX.Element; diff --git a/packages/grafana-ui/src/components/Card/Card.story.tsx b/packages/grafana-ui/src/components/Card/Card.story.tsx index 08893cf8d50..a6e06b499bd 100644 --- a/packages/grafana-ui/src/components/Card/Card.story.tsx +++ b/packages/grafana-ui/src/components/Card/Card.story.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { Story } from '@storybook/react'; +import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { Card, Props } from './Card'; -import mdx from './Card.mdx'; import { Button } from '../Button'; import { IconButton } from '../IconButton/IconButton'; -import { TagList } from '../Tags/TagList'; import { VerticalGroup } from '../Layout/Layout'; +import { TagList } from '../Tags/TagList'; + +import { Card, Props } from './Card'; +import mdx from './Card.mdx'; const logo = 'https://grafana.com/static/assets/img/apple-touch-icon.png'; diff --git a/packages/grafana-ui/src/components/Card/Card.test.tsx b/packages/grafana-ui/src/components/Card/Card.test.tsx index 9b147a42b01..87e3c5fff6d 100644 --- a/packages/grafana-ui/src/components/Card/Card.test.tsx +++ b/packages/grafana-ui/src/components/Card/Card.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; -import { Card } from './Card'; +import React from 'react'; + import { Button } from '../Button'; import { IconButton } from '../IconButton/IconButton'; +import { Card } from './Card'; + describe('Card', () => { it('should execute callback when clicked', () => { const callback = jest.fn(); diff --git a/packages/grafana-ui/src/components/Card/Card.tsx b/packages/grafana-ui/src/components/Card/Card.tsx index 996339811c8..f5056e9b232 100644 --- a/packages/grafana-ui/src/components/Card/Card.tsx +++ b/packages/grafana-ui/src/components/Card/Card.tsx @@ -1,10 +1,13 @@ -import React, { memo, cloneElement, FC, useMemo, useContext, ReactNode } from 'react'; import { css, cx } from '@emotion/css'; +import React, { memo, cloneElement, FC, useMemo, useContext, ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2, useTheme2 } from '../../themes'; -import { CardContainer, CardContainerProps, getCardContainerStyles } from './CardContainer'; import { getFocusStyles } from '../../themes/mixins'; +import { CardContainer, CardContainerProps, getCardContainerStyles } from './CardContainer'; + /** * @public */ @@ -232,12 +235,17 @@ const Meta = memo(({ children, className, separator = '|' }: ChildProps & { sepa const styles = useStyles2(getMetaStyles); let meta = children; + const filtered = React.Children.toArray(children).filter(Boolean); + if (!filtered.length) { + return null; + } + meta = filtered.map((element, i) => ( +
+ {element} +
+ )); // Join meta data elements by separator - if (Array.isArray(children) && separator) { - const filtered = React.Children.toArray(children).filter(Boolean); - if (!filtered.length) { - return null; - } + if (filtered.length > 1 && separator) { meta = filtered.reduce((prev, curr, i) => [ prev, @@ -261,6 +269,9 @@ const getMetaStyles = (theme: GrafanaTheme2) => ({ margin: theme.spacing(0.5, 0, 0), lineHeight: theme.typography.bodySmall.lineHeight, overflowWrap: 'anywhere', + }), + metadataItem: css({ + // Needed to allow for clickable children in metadata zIndex: 0, }), separator: css({ diff --git a/packages/grafana-ui/src/components/Card/CardContainer.tsx b/packages/grafana-ui/src/components/Card/CardContainer.tsx index 64bfe047e9c..3f81e998dc2 100644 --- a/packages/grafana-ui/src/components/Card/CardContainer.tsx +++ b/packages/grafana-ui/src/components/Card/CardContainer.tsx @@ -1,6 +1,8 @@ -import React, { HTMLAttributes } from 'react'; import { css, cx } from '@emotion/css'; +import React, { HTMLAttributes } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + import { styleMixins, stylesFactory, useStyles2, useTheme2 } from '../../themes'; /** diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx index a3ea1f80dc7..537f64a44b7 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx @@ -1,9 +1,12 @@ import { Story, Meta } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React from 'react'; + import { Cascader } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { CascaderOption, CascaderProps } from './Cascader'; import mdx from './Cascader.mdx'; -import React from 'react'; const onSelect = (val: string) => console.log(val); const options = [ diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx index 048756a565f..e2dafc03041 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { Cascader, CascaderOption, CascaderProps } from './Cascader'; import { act, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/dist/types/setup'; +import React from 'react'; + +import { Cascader, CascaderOption, CascaderProps } from './Cascader'; const options = [ { @@ -45,78 +47,86 @@ describe('Cascader', () => { const placeholder = 'cascader-placeholder'; describe('options from state change', () => { + let user: UserEvent; + beforeEach(() => { jest.useFakeTimers('modern'); + // Need to use delay: null here to work with fakeTimers + // see https://github.com/testing-library/user-event/issues/833 + user = userEvent.setup({ delay: null }); }); - it('displays updated options', () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it('displays updated options', async () => { render(); - act(() => { - userEvent.click(screen.getByPlaceholderText(placeholder)); - }); + await user.click(screen.getByPlaceholderText(placeholder)); expect(screen.getByText('Initial state option')).toBeInTheDocument(); expect(screen.queryByText('First')).not.toBeInTheDocument(); act(() => { jest.runAllTimers(); - userEvent.click(screen.getByPlaceholderText(placeholder)); }); + await user.click(screen.getByPlaceholderText(placeholder)); + expect(screen.queryByText('Initial state option')).not.toBeInTheDocument(); expect(screen.getByText('First')).toBeInTheDocument(); }); - it('filters updated results when searching', () => { + it('filters updated results when searching', async () => { render(); act(() => { jest.runAllTimers(); }); - userEvent.type(screen.getByPlaceholderText(placeholder), 'Third'); + await user.type(screen.getByPlaceholderText(placeholder), 'Third'); expect(screen.queryByText('Second')).not.toBeInTheDocument(); expect(screen.getByText('First / Third')).toBeInTheDocument(); }); }); - it('filters results when searching', () => { + it('filters results when searching', async () => { render(); - userEvent.type(screen.getByPlaceholderText(placeholder), 'Third'); + await userEvent.type(screen.getByPlaceholderText(placeholder), 'Third'); expect(screen.queryByText('Second')).not.toBeInTheDocument(); expect(screen.getByText('First / Third')).toBeInTheDocument(); }); - it('displays selected value with all levels when displayAllSelectedLevels is true and selecting a value from the search', () => { + it('displays selected value with all levels when displayAllSelectedLevels is true and selecting a value from the search', async () => { render( ); - userEvent.type(screen.getByPlaceholderText(placeholder), 'Third'); - userEvent.click(screen.getByText('First / Third')); + await userEvent.type(screen.getByPlaceholderText(placeholder), 'Third'); + await userEvent.click(screen.getByText('First / Third')); expect(screen.getByDisplayValue('First / Third')).toBeInTheDocument(); }); - it('displays all levels selected with default separator when displayAllSelectedLevels is true', () => { + it('displays all levels selected with default separator when displayAllSelectedLevels is true', async () => { render( {}} /> ); expect(screen.queryByDisplayValue('First/Second')).not.toBeInTheDocument(); - userEvent.click(screen.getByPlaceholderText(placeholder)); + await userEvent.click(screen.getByPlaceholderText(placeholder)); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(screen.getByText('First'), undefined, { skipPointerEventsCheck: true }); - userEvent.click(screen.getByText('Second'), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(screen.getByText('First'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.click(screen.getByText('Second'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); expect(screen.getByDisplayValue('First/Second')).toBeInTheDocument(); }); - it('displays all levels selected with separator passed in when displayAllSelectedLevels is true', () => { + it('displays all levels selected with separator passed in when displayAllSelectedLevels is true', async () => { const separator = ','; render( @@ -131,34 +141,34 @@ describe('Cascader', () => { expect(screen.queryByDisplayValue('First/Second')).not.toBeInTheDocument(); - userEvent.click(screen.getByPlaceholderText(placeholder)); + await userEvent.click(screen.getByPlaceholderText(placeholder)); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(screen.getByText('First'), undefined, { skipPointerEventsCheck: true }); - userEvent.click(screen.getByText('Second'), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(screen.getByText('First'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.click(screen.getByText('Second'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); expect(screen.getByDisplayValue(`First${separator}Second`)).toBeInTheDocument(); }); - it('displays last level selected when displayAllSelectedLevels is false', () => { + it('displays last level selected when displayAllSelectedLevels is false', async () => { render( ); - userEvent.click(screen.getByPlaceholderText(placeholder)); + await userEvent.click(screen.getByPlaceholderText(placeholder)); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(screen.getByText('First'), undefined, { skipPointerEventsCheck: true }); - userEvent.click(screen.getByText('Second'), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(screen.getByText('First'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.click(screen.getByText('Second'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); expect(screen.getByDisplayValue('Second')).toBeInTheDocument(); }); - it('displays last level selected when displayAllSelectedLevels is not passed in', () => { + it('displays last level selected when displayAllSelectedLevels is not passed in', async () => { render(); - userEvent.click(screen.getByPlaceholderText(placeholder)); + await userEvent.click(screen.getByPlaceholderText(placeholder)); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(screen.getByText('First'), undefined, { skipPointerEventsCheck: true }); - userEvent.click(screen.getByText('Second'), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(screen.getByText('First'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.click(screen.getByText('Second'), { pointerEventsCheck: PointerEventsCheckLevel.Never }); expect(screen.getByDisplayValue('Second')).toBeInTheDocument(); }); diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.tsx index a6f57c14759..5b061f0e943 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.tsx @@ -1,13 +1,15 @@ -import React from 'react'; -import { Icon } from '../Icon/Icon'; -import RCCascader from 'rc-cascader'; - -import { Select } from '../Select/Select'; -import { Input } from '../Input/Input'; -import { SelectableValue } from '@grafana/data'; import { css } from '@emotion/css'; -import { onChangeCascader } from './optionMappings'; import memoizeOne from 'memoize-one'; +import RCCascader from 'rc-cascader'; +import React from 'react'; + +import { SelectableValue } from '@grafana/data'; + +import { Icon } from '../Icon/Icon'; +import { Input } from '../Input/Input'; +import { Select } from '../Select/Select'; + +import { onChangeCascader } from './optionMappings'; export interface CascaderProps { /** The separator between levels in the search */ diff --git a/packages/grafana-ui/src/components/Cascader/optionMappings.ts b/packages/grafana-ui/src/components/Cascader/optionMappings.ts index c616a09d603..6d490609d70 100644 --- a/packages/grafana-ui/src/components/Cascader/optionMappings.ts +++ b/packages/grafana-ui/src/components/Cascader/optionMappings.ts @@ -1,4 +1,5 @@ import { SingleValueType, BaseOptionType as RCCascaderOption } from 'rc-cascader/lib/Cascader'; + import { CascaderOption } from './Cascader'; type onChangeType = ((values: string[], options: CascaderOption[]) => void) | undefined; diff --git a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.story.tsx b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.story.tsx index 8746e6b2904..e9b5a6b1e50 100644 --- a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.story.tsx +++ b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.story.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; -import { ClickOutsideWrapper } from './ClickOutsideWrapper'; +import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { ClickOutsideWrapper } from './ClickOutsideWrapper'; import mdx from './ClickOutsideWrapper.mdx'; export default { diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.internal.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.internal.tsx index 8bc335d7e32..db7a783962f 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.internal.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.story.internal.tsx @@ -1,8 +1,10 @@ -import React, { useState } from 'react'; import { Story, Meta } from '@storybook/react'; +import React, { useState } from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { ClipboardButton, Props } from './ClipboardButton'; import { Input } from '../Forms/Legacy/Input/Input'; + +import { ClipboardButton, Props } from './ClipboardButton'; import mdx from './ClipboardButton.mdx'; export default { diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx index 63c1aba06c9..61f7a19debe 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useRef } from 'react'; + import { Button, ButtonProps } from '../Button'; /** @deprecated Will be removed in next major release */ diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx index 6e02c8cf7bc..a86051a369e 100644 --- a/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.story.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { CollapsableSection } from './CollapsableSection'; import mdx from './CollapsableSection.mdx'; diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx index c58c7b8c3a1..13fff992cb0 100644 --- a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx @@ -1,9 +1,11 @@ -import React, { FC, ReactNode, useRef, useState } from 'react'; -import { uniqueId } from 'lodash'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '../../themes'; -import { Icon, Spinner } from '..'; +import { uniqueId } from 'lodash'; +import React, { FC, ReactNode, useRef, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + +import { Icon, Spinner } from '..'; +import { useStyles2 } from '../../themes'; import { getFocusStyles } from '../../themes/mixins'; export interface Props { diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx index b7b00dcbd08..9d8fc0ca0d5 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { Collapse, ControlledCollapse } from './Collapse'; -import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { Collapse, ControlledCollapse } from './Collapse'; import mdx from './Collapse.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.tsx index 2b081551287..13f871bf326 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.tsx @@ -1,7 +1,8 @@ import { css, cx } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; import React, { FunctionComponent, useState } from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes/ThemeContext'; import { Icon } from '../Icon/Icon'; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx index 684dd41c53b..5877ba3f959 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx @@ -1,12 +1,14 @@ +import { cx, css } from '@emotion/css'; +import { debounce } from 'lodash'; import React from 'react'; import tinycolor from 'tinycolor2'; -import { debounce } from 'lodash'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; +import { Input } from '../Input/Input'; import { ColorPickerProps } from './ColorPickerPopover'; -import { Input } from '../Input/Input'; -import { useStyles2 } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; -import { cx, css } from '@emotion/css'; interface ColorInputState { previousColor: string; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx index 1312169c203..200154f0ff0 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx @@ -1,12 +1,15 @@ -import React from 'react'; -import { Meta, Story } from '@storybook/react'; -import { SeriesColorPicker, ColorPicker } from '@grafana/ui'; import { action } from '@storybook/addon-actions'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import { SeriesColorPicker, ColorPicker } from '@grafana/ui'; + import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; -import { ColorPickerProps } from './ColorPickerPopover'; + import mdx from './ColorPicker.mdx'; +import { ColorPickerProps } from './ColorPickerPopover'; export default { title: 'Pickers and Editors/ColorPicker', diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.test.tsx index 00a703f3eae..78350a9108a 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import renderer from 'react-test-renderer'; + import { ColorPicker } from './ColorPicker'; import { ColorSwatch } from './ColorSwatch'; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 4d96440b69b..453c7d430c6 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,14 +1,16 @@ -import React, { Component, createRef } from 'react'; -import { PopoverController } from '../Tooltip/PopoverController'; -import { Popover } from '../Tooltip/Popover'; -import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover'; -import { GrafanaTheme2 } from '@grafana/data'; -import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; - import { css } from '@emotion/css'; +import React, { Component, createRef } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { withTheme2, stylesFactory } from '../../themes'; -import { ColorSwatch } from './ColorSwatch'; import { closePopover } from '../../utils/closePopover'; +import { Popover } from '../Tooltip/Popover'; +import { PopoverController } from '../Tooltip/PopoverController'; + +import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover'; +import { ColorSwatch } from './ColorSwatch'; +import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; /** * If you need custom trigger for the color picker you can do that with a render prop pattern and supply a function diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx index 5b04d0da970..a26e055b490 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx @@ -1,9 +1,9 @@ -import { ColorPickerPopover } from './ColorPickerPopover'; - import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; +import { ColorPickerPopover } from './ColorPickerPopover'; +import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; + export default { title: 'Pickers and Editors/ColorPicker/Popovers', component: ColorPickerPopover, diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx index 1c0ab924223..cc60ee3c0b7 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx @@ -1,30 +1,28 @@ -import React from 'react'; -import { act, render, screen } from '@testing-library/react'; -import { ColorPickerPopover } from './ColorPickerPopover'; -import { createTheme } from '@grafana/data'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { createTheme } from '@grafana/data'; + +import { ColorPickerPopover } from './ColorPickerPopover'; describe('ColorPickerPopover', () => { const theme = createTheme(); - it('should be tabbable', () => { + it('should be tabbable', async () => { render( {}} />); const color = screen.getByRole('button', { name: 'dark-red color' }); const customTab = screen.getByRole('button', { name: 'Custom' }); - act(() => { - userEvent.tab(); - }); + await userEvent.tab(); expect(customTab).toHaveFocus(); - act(() => { - userEvent.tab(); - }); + await userEvent.tab(); expect(color).toHaveFocus(); }); describe('rendering', () => { - it('should render provided color as selected if color provided by name', () => { + it('should render provided color as selected if color provided by name', async () => { render( {}} />); const color = screen.getByRole('button', { name: 'green color' }); const colorSwatchWrapper = screen.getAllByTestId('data-testid-colorswatch'); @@ -32,9 +30,7 @@ describe('ColorPickerPopover', () => { expect(color).toBeInTheDocument(); expect(colorSwatchWrapper[0]).toBeInTheDocument(); - act(() => { - userEvent.click(colorSwatchWrapper[0]); - }); + await userEvent.click(colorSwatchWrapper[0]); expect(color).toHaveStyle('box-shadow: inset 0 0 0 2px #73BF69,inset 0 0 0 4px #000000'); }); }); @@ -42,23 +38,19 @@ describe('ColorPickerPopover', () => { describe('named colors support', () => { const onChangeSpy = jest.fn(); - it('should pass hex color value to onChange prop by default', () => { + it('should pass hex color value to onChange prop by default', async () => { render(); const color = screen.getByRole('button', { name: 'red color' }); - act(() => { - userEvent.click(color); - }); + await userEvent.click(color); expect(onChangeSpy).toBeCalledTimes(1); expect(onChangeSpy).toBeCalledWith(theme.visualization.getColorByName('red')); }); - it('should pass color name to onChange prop when named colors enabled', () => { + it('should pass color name to onChange prop when named colors enabled', async () => { render(); const color = screen.getByRole('button', { name: 'red color' }); - act(() => { - userEvent.click(color); - }); + await userEvent.click(color); expect(onChangeSpy).toBeCalledTimes(2); expect(onChangeSpy).toBeCalledWith(theme.visualization.getColorByName('red')); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index 4bfa15710c2..3a77661ac44 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { NamedColorsPalette } from './NamedColorsPalette'; -import { PopoverContentProps } from '../Tooltip'; -import SpectrumPalette from './SpectrumPalette'; -import { Themeable2 } from '../../types/theme'; -import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation'; import { css } from '@emotion/css'; -import { GrafanaTheme2, colorManipulator } from '@grafana/data'; -import { stylesFactory, withTheme2 } from '../../themes'; import { FocusScope } from '@react-aria/focus'; +import React from 'react'; + +import { GrafanaTheme2, colorManipulator } from '@grafana/data'; + +import { stylesFactory, withTheme2 } from '../../themes'; +import { Themeable2 } from '../../types/theme'; +import { PopoverContentProps } from '../Tooltip'; + +import { NamedColorsPalette } from './NamedColorsPalette'; +import SpectrumPalette from './SpectrumPalette'; +import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation'; export type ColorPickerChangeHandler = (color: string) => void; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorSwatch.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorSwatch.tsx index 99417069381..309b1f852a3 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorSwatch.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorSwatch.tsx @@ -1,10 +1,12 @@ +import { css } from '@emotion/css'; import { useFocusRing } from '@react-aria/focus'; import React from 'react'; import tinycolor from 'tinycolor2'; -import { useTheme2 } from '../../themes/ThemeContext'; -import { selectors } from '@grafana/e2e-selectors'; + import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; +import { selectors } from '@grafana/e2e-selectors'; + +import { useTheme2 } from '../../themes/ThemeContext'; /** @internal */ export enum ColorSwatchVariant { diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx index 607eaf03f4b..016d155d242 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx @@ -1,12 +1,15 @@ -import React, { FunctionComponent } from 'react'; -import { GrafanaTheme2, ThemeVizHue } from '@grafana/data'; -import { Property } from 'csstype'; -import { ColorSwatch, ColorSwatchVariant } from './ColorSwatch'; -import { upperFirst } from 'lodash'; -import { useStyles2 } from '../../themes/ThemeContext'; import { css } from '@emotion/css'; +import { Property } from 'csstype'; +import { upperFirst } from 'lodash'; +import React, { FunctionComponent } from 'react'; + +import { GrafanaTheme2, ThemeVizHue } from '@grafana/data'; + +import { useStyles2 } from '../../themes/ThemeContext'; import { reverseMap } from '../../utils/reverseMap'; +import { ColorSwatch, ColorSwatchVariant } from './ColorSwatch'; + interface NamedColorsGroupProps { hue: ThemeVizHue; selectedColor?: Property.Color; diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx index cb24e5ce367..f5f20cca365 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx @@ -1,8 +1,10 @@ -import React, { useState } from 'react'; -import { NamedColorsPalette, NamedColorsPaletteProps } from './NamedColorsPalette'; import { Meta, Story } from '@storybook/react'; +import React, { useState } from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './ColorPicker.mdx'; +import { NamedColorsPalette, NamedColorsPaletteProps } from './NamedColorsPalette'; export default { title: 'Pickers and Editors/ColorPicker/Palettes/NamedColorsPalette', diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx index 023dad1f14a..21409e560bb 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { NamedColorsPalette } from './NamedColorsPalette'; +import React from 'react'; + import { createTheme } from '@grafana/data'; + import { ColorSwatch } from './ColorSwatch'; +import { NamedColorsPalette } from './NamedColorsPalette'; describe('NamedColorsPalette', () => { const theme = createTheme(); diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx index 548b814c354..266eb238aaa 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import NamedColorsGroup from './NamedColorsGroup'; -import { ColorSwatch } from './ColorSwatch'; -import { useStyles2, useTheme2 } from '../../themes/ThemeContext'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2, useTheme2 } from '../../themes/ThemeContext'; + +import { ColorSwatch } from './ColorSwatch'; +import NamedColorsGroup from './NamedColorsGroup'; export interface NamedColorsPaletteProps { color?: string; diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 0cf754b2db3..1d6de01a9e0 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -1,11 +1,12 @@ +import { css } from '@emotion/css'; import React, { FunctionComponent } from 'react'; -import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover'; -import { PopoverContentProps } from '../Tooltip'; -import { Switch } from '../Forms/Legacy/Switch/Switch'; -import { css } from '@emotion/css'; import { withTheme2, useStyles } from '../../themes'; import { Button } from '../Button'; +import { Switch } from '../Forms/Legacy/Switch/Switch'; +import { PopoverContentProps } from '../Tooltip'; + +import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover'; export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopoverContentProps { yaxis?: number; diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx index dc6713e3e93..8b50293f658 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import SpectrumPalette from './SpectrumPalette'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; + import mdx from './ColorPicker.mdx'; +import SpectrumPalette from './SpectrumPalette'; export default { title: 'Pickers and Editors/ColorPicker/Palettes/SpectrumPalette', diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx index 3b6a7ca51fd..7006a1e8636 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.tsx @@ -1,12 +1,14 @@ -import React, { useMemo, useState } from 'react'; - -import { RgbaStringColorPicker } from 'react-colorful'; -import tinycolor from 'tinycolor2'; -import ColorInput from './ColorInput'; -import { GrafanaTheme2, colorManipulator } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { useStyles2, useTheme2 } from '../../themes'; +import React, { useMemo, useState } from 'react'; +import { RgbaStringColorPicker } from 'react-colorful'; import { useThrottleFn } from 'react-use'; +import tinycolor from 'tinycolor2'; + +import { GrafanaTheme2, colorManipulator } from '@grafana/data'; + +import { useStyles2, useTheme2 } from '../../themes'; + +import ColorInput from './ColorInput'; export interface SpectrumPaletteProps { color: string; diff --git a/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts b/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts index 145cf5d5c67..9f58ba70285 100644 --- a/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts +++ b/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts @@ -1,4 +1,5 @@ import { deprecationWarning } from '@grafana/data'; + import { ColorPickerProps } from './ColorPickerPopover'; export const warnAboutColorPickerPropsDeprecation = (componentName: string, props: ColorPickerProps) => { diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx index cdf99180555..adab9546c4f 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.story.tsx @@ -1,12 +1,15 @@ -import React from 'react'; -import { Meta, Story } from '@storybook/react'; -import { ConfirmButton } from '@grafana/ui'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { action } from '@storybook/addon-actions'; +import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import { ConfirmButton } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Button } from '../Button'; -import { DeleteButton } from './DeleteButton'; + import { Props } from './ConfirmButton'; import mdx from './ConfirmButton.mdx'; +import { DeleteButton } from './DeleteButton'; export default { title: 'Buttons/ConfirmButton', diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx index c9e4d9cd4f3..6a91f927413 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx @@ -1,12 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; -import { ConfirmButton } from './ConfirmButton'; import { expect } from '../../../../../public/test/lib/common'; +import { ConfirmButton } from './ConfirmButton'; + describe('ConfirmButton', () => { - it('should show confirm delete when clicked', () => { + it('should show confirm delete when clicked', async () => { const onConfirm = jest.fn(); render( @@ -17,18 +18,18 @@ describe('ConfirmButton', () => { // Confirm button should not be visible before clicking the Delete button expect(screen.queryByRole('button', { name: 'Confirm delete' })).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Delete' })); + await userEvent.click(screen.getByRole('button', { name: 'Delete' })); // Confirm button should now be visible expect(screen.getByRole('button', { name: 'Confirm delete' })).toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Confirm delete' })); + await userEvent.click(screen.getByRole('button', { name: 'Confirm delete' })); expect(onConfirm).toHaveBeenCalled(); // Confirm button should be visible if closeOnConfirm is false expect(screen.queryByRole('button', { name: 'Confirm delete' })).toBeInTheDocument(); }); - it('should hide confirm delete when closeOnConfirm is true', () => { + it('should hide confirm delete when closeOnConfirm is true', async () => { render( {}} closeOnConfirm={true}> Delete @@ -38,17 +39,17 @@ describe('ConfirmButton', () => { // Confirm button should not be visible before clicking the Delete button expect(screen.queryByRole('button', { name: 'Confirm delete' })).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Delete' })); + await userEvent.click(screen.getByRole('button', { name: 'Delete' })); // Confirm button should now be visible expect(screen.getByRole('button', { name: 'Confirm delete' })).toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Confirm delete' })); + await userEvent.click(screen.getByRole('button', { name: 'Confirm delete' })); // Confirm button should not be visible if closeOnConfirm is true expect(screen.queryByRole('button', { name: 'Confirm delete' })).not.toBeInTheDocument(); }); - it('should show cancel when clicked', () => { + it('should show cancel when clicked', async () => { const onCancel = jest.fn(); render( {}}> @@ -59,11 +60,11 @@ describe('ConfirmButton', () => { // Cancel button should not be visible before clicking the Delete button expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Delete' })); + await userEvent.click(screen.getByRole('button', { name: 'Delete' })); // Cancel button should now be visible expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Cancel' })); + await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); expect(onCancel).toHaveBeenCalled(); // Cancel button should not be visible after click diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx index d41edf0b699..a9c39369c6d 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx @@ -1,7 +1,9 @@ -import React, { PureComponent, SyntheticEvent } from 'react'; import { cx, css } from '@emotion/css'; -import { stylesFactory, withTheme } from '../../themes'; +import React, { PureComponent, SyntheticEvent } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + +import { stylesFactory, withTheme } from '../../themes'; import { Themeable } from '../../types'; import { ComponentSize } from '../../types/size'; import { Button, ButtonVariant } from '../Button'; diff --git a/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx b/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx index f383a31ef5d..c0e5014d5d9 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/DeleteButton.tsx @@ -1,8 +1,10 @@ import React, { FC } from 'react'; -import { ConfirmButton } from './ConfirmButton'; + import { ComponentSize } from '../../types/size'; import { Button } from '../Button'; +import { ConfirmButton } from './ConfirmButton'; + export interface Props { /** Confirm action callback */ onConfirm(): void; diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.story.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.story.tsx index e0c265d4412..11fef1700df 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.story.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.story.tsx @@ -1,10 +1,13 @@ -import React from 'react'; -import { Meta, Story } from '@storybook/react'; import { action } from '@storybook/addon-actions'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Meta, Story } from '@storybook/react'; +import React from 'react'; + import { ConfirmModal } from '@grafana/ui'; -import mdx from './ConfirmModal.mdx'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { ConfirmModalProps } from './ConfirmModal'; +import mdx from './ConfirmModal.mdx'; const defaultExcludes = ['onConfirm', 'onDismiss', 'onAlternative']; diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx index 50a9482c9af..8c7a2f93c88 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import { render, screen, within } from '@testing-library/react'; +import React from 'react'; import { ConfirmModal } from './ConfirmModal'; diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx index 99d5bf676c6..8139228a81c 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx @@ -1,13 +1,14 @@ -import React, { useEffect, useRef, useState } from 'react'; import { css } from '@emotion/css'; +import React, { useEffect, useRef, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Modal } from '../Modal/Modal'; +import { HorizontalGroup, Input } from '..'; +import { useStyles2 } from '../../themes'; import { IconName } from '../../types/icon'; import { Button } from '../Button'; -import { useStyles2 } from '../../themes'; -import { HorizontalGroup, Input } from '..'; +import { Modal } from '../Modal/Modal'; export interface ConfirmModalProps { /** Toggle modal's open/closed state */ diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx index edcc1948814..077dc698184 100644 --- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.story.tsx @@ -1,12 +1,14 @@ import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { IconButton } from '../IconButton/IconButton'; -import { ContextMenu } from './ContextMenu'; -import { WithContextMenu } from './WithContextMenu'; -import mdx from './ContextMenu.mdx'; import { MenuGroup } from '../Menu/MenuGroup'; import { MenuItem } from '../Menu/MenuItem'; +import { ContextMenu } from './ContextMenu'; +import mdx from './ContextMenu.mdx'; +import { WithContextMenu } from './WithContextMenu'; + export default { title: 'General/ContextMenu', component: ContextMenu, diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx index d4312d6a3db..bde5b951c51 100644 --- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx @@ -1,8 +1,10 @@ import React, { useRef, useState, useLayoutEffect } from 'react'; -import { selectors } from '@grafana/e2e-selectors'; import { useClickAway } from 'react-use'; -import { Portal } from '../Portal/Portal'; + +import { selectors } from '@grafana/e2e-selectors'; + import { Menu } from '../Menu/Menu'; +import { Portal } from '../Portal/Portal'; export interface ContextMenuProps { /** Starting horizontal position for the menu */ diff --git a/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx index 92c862c8c77..c502306b351 100644 --- a/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/WithContextMenu.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; + import { ContextMenu } from '../ContextMenu/ContextMenu'; interface WithContextMenuProps { diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx index 32375a5dc29..0c8c84a229d 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import renderer from 'react-test-renderer'; + import { CustomScrollbar } from './CustomScrollbar'; describe('CustomScrollbar', () => { diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index fa228b87877..a5091495894 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -1,9 +1,11 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; import classNames from 'classnames'; import { isNil } from 'lodash'; import React, { FC, RefCallback, useCallback, useEffect, useRef } from 'react'; import Scrollbars, { positionValues } from 'react-custom-scrollbars-2'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; export type ScrollbarPosition = positionValues; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx index 95d8b8d4160..e9309429c50 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkButton.tsx @@ -1,5 +1,7 @@ -import { Field, LinkModel } from '@grafana/data'; import React from 'react'; + +import { Field, LinkModel } from '@grafana/data'; + import { ButtonProps, Button } from '../Button'; type DataLinkButtonProps = { diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkEditor.tsx index b433d029a7f..820fae0b5bb 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkEditor.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkEditor.tsx @@ -1,11 +1,14 @@ -import React, { ChangeEvent } from 'react'; -import { VariableSuggestion, GrafanaTheme2, DataLink } from '@grafana/data'; -import { Switch } from '../Switch/Switch'; import { css } from '@emotion/css'; +import React, { ChangeEvent } from 'react'; + +import { VariableSuggestion, GrafanaTheme2, DataLink } from '@grafana/data'; + import { useStyles2 } from '../../themes/index'; -import { DataLinkInput } from './DataLinkInput'; import { Field } from '../Forms/Field'; import { Input } from '../Input/Input'; +import { Switch } from '../Switch/Switch'; + +import { DataLinkInput } from './DataLinkInput'; interface DataLinkEditorProps { index: number; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx index 2395c016e5d..c6d958ed9c0 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx @@ -1,24 +1,24 @@ -import React, { memo, RefObject, useEffect, useMemo, useRef, useState } from 'react'; -import usePrevious from 'react-use/lib/usePrevious'; -import { DataLinkSuggestions } from './DataLinkSuggestions'; -import { makeValue } from '../../index'; -import { SelectionReference } from './SelectionReference'; -import { Portal } from '../index'; - -// @ts-ignore +import { css, cx } from '@emotion/css'; import Prism, { Grammar, LanguageMap } from 'prismjs'; -import { Editor } from '@grafana/slate-react'; +import React, { memo, RefObject, useEffect, useMemo, useRef, useState } from 'react'; +import { Popper as ReactPopper } from 'react-popper'; +import usePrevious from 'react-use/lib/usePrevious'; import { Value } from 'slate'; import Plain from 'slate-plain-serializer'; -import { Popper as ReactPopper } from 'react-popper'; -import { css, cx } from '@emotion/css'; -import { SlatePrism } from '../../slate-plugins'; -import { SCHEMA } from '../../utils/slate'; -import { useStyles2 } from '../../themes'; import { DataLinkBuiltInVars, GrafanaTheme2, VariableOrigin, VariableSuggestion } from '@grafana/data'; -import { getInputStyles } from '../Input/Input'; +import { Editor } from '@grafana/slate-react'; + +import { makeValue } from '../../index'; +import { SlatePrism } from '../../slate-plugins'; +import { useStyles2 } from '../../themes'; +import { SCHEMA } from '../../utils/slate'; import CustomScrollbar from '../CustomScrollbar/CustomScrollbar'; +import { getInputStyles } from '../Input/Input'; +import { Portal } from '../index'; + +import { DataLinkSuggestions } from './DataLinkSuggestions'; +import { SelectionReference } from './SelectionReference'; const modulo = (a: number, n: number) => a - n * Math.floor(a / n); diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx index e3dff49688c..fe493efe315 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx @@ -1,10 +1,12 @@ -import { VariableSuggestion, GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; import { groupBy, capitalize } from 'lodash'; import React, { useRef, useMemo } from 'react'; import useClickAway from 'react-use/lib/useClickAway'; -import { List } from '../index'; + +import { VariableSuggestion, GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; +import { List } from '../index'; interface DataLinkSuggestionsProps { activeRef?: React.RefObject; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.test.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.test.tsx index 9ac75b37da0..8deb2ff2f50 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.test.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { DataLinksContextMenu } from './DataLinksContextMenu'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; +import { DataLinksContextMenu } from './DataLinksContextMenu'; + const fakeAriaLabel = 'fake aria label'; describe('DataLinksContextMenu', () => { it('renders context menu when there are more than one data links', () => { diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx index 2756b65ae41..5a700355ae6 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx @@ -1,9 +1,11 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { FieldConfig, LinkModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { css } from '@emotion/css'; -import { WithContextMenu } from '../ContextMenu/WithContextMenu'; + import { linkModelToContextMenuItems } from '../../utils/dataLinks'; +import { WithContextMenu } from '../ContextMenu/WithContextMenu'; import { MenuGroup, MenuItemsGroup } from '../Menu/MenuGroup'; import { MenuItem } from '../Menu/MenuItem'; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx index 3d3a25fd749..3adecc87e0c 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinkEditorModalContent.tsx @@ -1,8 +1,10 @@ -import { DataFrame, DataLink, VariableSuggestion } from '@grafana/data'; import React, { FC, useState } from 'react'; -import { DataLinkEditor } from '../DataLinkEditor'; + +import { DataFrame, DataLink, VariableSuggestion } from '@grafana/data'; + import { Button } from '../../Button'; import { Modal } from '../../Modal/Modal'; +import { DataLinkEditor } from '../DataLinkEditor'; interface DataLinkEditorModalContentProps { link: DataLink; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx index c4e56940a23..c0db22200d1 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx @@ -1,12 +1,15 @@ -import { DataFrame, DataLink, GrafanaTheme2, VariableSuggestion } from '@grafana/data'; -import React, { useState } from 'react'; import { css } from '@emotion/css'; -import { Button } from '../../Button/Button'; import { cloneDeep } from 'lodash'; -import { Modal } from '../../Modal/Modal'; +import React, { useState } from 'react'; + +import { DataFrame, DataLink, GrafanaTheme2, VariableSuggestion } from '@grafana/data'; + import { stylesFactory, useTheme2 } from '../../../themes'; -import { DataLinksListItem } from './DataLinksListItem'; +import { Button } from '../../Button/Button'; +import { Modal } from '../../Modal/Modal'; + import { DataLinkEditorModalContent } from './DataLinkEditorModalContent'; +import { DataLinksListItem } from './DataLinksListItem'; interface DataLinksInlineEditorProps { links?: DataLink[]; diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx index c2655b8d8a5..4e9d27d7f3d 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { DataLinksListItem, DataLinksListItemProps } from './DataLinksListItem'; const baseLink = { diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx index 5fc55c310a2..03fcfdd6623 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx @@ -1,9 +1,11 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; + import { DataFrame, DataLink, GrafanaTheme2 } from '@grafana/data'; + import { stylesFactory, useTheme2 } from '../../../themes'; -import { HorizontalGroup, VerticalGroup } from '../../Layout/Layout'; import { IconButton } from '../../IconButton/IconButton'; +import { HorizontalGroup, VerticalGroup } from '../../Layout/Layout'; export interface DataLinksListItemProps { index: number; diff --git a/packages/grafana-ui/src/components/DataLinks/FieldLinkList.tsx b/packages/grafana-ui/src/components/DataLinks/FieldLinkList.tsx index 0124ac5d74f..548131230d3 100644 --- a/packages/grafana-ui/src/components/DataLinks/FieldLinkList.tsx +++ b/packages/grafana-ui/src/components/DataLinks/FieldLinkList.tsx @@ -1,8 +1,11 @@ -import { Field, GrafanaTheme, LinkModel } from '@grafana/data'; import { css } from '@emotion/css'; import React from 'react'; + +import { Field, GrafanaTheme, LinkModel } from '@grafana/data'; + import { useStyles } from '../../themes'; import { Icon } from '../Icon/Icon'; + import { DataLinkButton } from './DataLinkButton'; type Props = { diff --git a/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx index 79135144cbd..ac2c050f512 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx @@ -1,8 +1,10 @@ -import { DataSourceInstanceSettings, DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data'; import React, { useMemo } from 'react'; -import { Switch } from '../Forms/Legacy/Switch/Switch'; + +import { DataSourceInstanceSettings, DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data'; + import { InlineField } from '../Forms/InlineField'; import { InlineFieldRow } from '../Forms/InlineFieldRow'; +import { Switch } from '../Forms/Legacy/Switch/Switch'; import { Select } from '../Select/Select'; interface Props extends Pick, 'options' | 'onOptionsChange'> { diff --git a/packages/grafana-ui/src/components/DataSourceSettings/BasicAuthSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/BasicAuthSettings.tsx index e7459507628..4755f7cb59d 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/BasicAuthSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/BasicAuthSettings.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { InlineField } from '../..'; -import { HttpSettingsProps } from './types'; import { FormField } from '../FormField/FormField'; import { SecretFormField } from '../SecretFormField/SecretFormField'; +import { HttpSettingsProps } from './types'; + export const BasicAuthSettings: React.FC = ({ dataSourceConfig, onChange }) => { const password = dataSourceConfig.secureJsonData ? dataSourceConfig.secureJsonData.basicAuthPassword : ''; diff --git a/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx b/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx index 56e238464a8..20b57fc6576 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/CertificationKey.tsx @@ -1,9 +1,10 @@ import React, { ChangeEvent, MouseEvent, FC } from 'react'; -import { Input } from '../Input/Input'; + import { Button } from '../Button'; -import { TextArea } from '../TextArea/TextArea'; import { InlineField } from '../Forms/InlineField'; import { InlineFieldRow } from '../Forms/InlineFieldRow'; +import { Input } from '../Input/Input'; +import { TextArea } from '../TextArea/TextArea'; interface Props { label: string; diff --git a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx index c7205d4fb09..dc39d17f41b 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { CustomHeadersSettings, Props } from './CustomHeadersSettings'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { CustomHeadersSettings, Props } from './CustomHeadersSettings'; const setup = (propOverrides?: object) => { const onChange = jest.fn(); @@ -54,13 +55,13 @@ function assertRowCount(configuredInputCount: number, passwordInputCount: number } describe('Render', () => { - it('should add a new header', () => { + it('should add a new header', async () => { setup(); const b = screen.getByRole('button', { name: 'Add header' }); expect(b).toBeInTheDocument(); assertRowCount(0, 0); - userEvent.click(b); + await userEvent.click(b); assertRowCount(0, 1); }); @@ -71,7 +72,7 @@ describe('Render', () => { expect(b.getAttribute('type')).toBe('button'); }); - it('should remove a header', () => { + it('should remove a header', async () => { const { onChange } = setup({ dataSourceConfig: { jsonData: { @@ -87,14 +88,14 @@ describe('Render', () => { assertRowCount(1, 0); - userEvent.click(b); + await userEvent.click(b); assertRowCount(0, 0); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange.mock.calls[0][0].jsonData).toStrictEqual({}); }); - it('when removing a just-created header, it should clean up secureJsonData', () => { + it('when removing a just-created header, it should clean up secureJsonData', async () => { const { onChange } = setup({ dataSourceConfig: { jsonData: { @@ -109,7 +110,7 @@ describe('Render', () => { // we remove the row const removeButton = screen.getByRole('button', { name: 'Remove header' }); expect(removeButton).toBeInTheDocument(); - userEvent.click(removeButton); + await userEvent.click(removeButton); assertRowCount(0, 0); expect(onChange).toHaveBeenCalled(); @@ -119,7 +120,7 @@ describe('Render', () => { expect(lastCall[0].secureJsonData).not.toHaveProperty('httpHeaderValue1'); }); - it('should reset a header', () => { + it('should reset a header', async () => { setup({ dataSourceConfig: { jsonData: { @@ -135,7 +136,7 @@ describe('Render', () => { expect(b).toBeInTheDocument(); assertRowCount(1, 0); - userEvent.click(b); + await userEvent.click(b); assertRowCount(0, 1); }); }); diff --git a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx index 586221ab2c4..ade08af90b7 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.tsx @@ -1,12 +1,14 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; import { uniqueId } from 'lodash'; +import React, { PureComponent } from 'react'; + import { DataSourceSettings } from '@grafana/data'; + +import { stylesFactory } from '../../themes'; import { Button } from '../Button'; import { FormField } from '../FormField/FormField'; import { Icon } from '../Icon/Icon'; import { SecretFormField } from '../SecretFormField/SecretFormField'; -import { stylesFactory } from '../../themes'; export interface CustomHeader { id: string; diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.story.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.story.tsx index 913cf939838..17ebfcc7b66 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.story.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.story.tsx @@ -1,8 +1,10 @@ import React from 'react'; + import { DataSourceSettings } from '@grafana/data'; -import { DataSourceHttpSettings } from './DataSourceHttpSettings'; import { UseState } from '../../utils/storybook/UseState'; + +import { DataSourceHttpSettings } from './DataSourceHttpSettings'; import mdx from './DataSourceHttpSettings.mdx'; const settingsMock: DataSourceSettings = { diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx index 6e44460f34f..9cf6da6aba2 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx @@ -1,20 +1,23 @@ -import React, { useState, useCallback } from 'react'; import { css, cx } from '@emotion/css'; +import React, { useState, useCallback } from 'react'; + import { DataSourceSettings, SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { BasicAuthSettings } from './BasicAuthSettings'; -import { HttpProxySettings } from './HttpProxySettings'; -import { TLSAuthSettings } from './TLSAuthSettings'; -import { CustomHeadersSettings } from './CustomHeadersSettings'; -import { Select } from '../Select/Select'; + +import { useTheme } from '../../themes'; +import { FormField } from '../FormField/FormField'; +import { InlineFormLabel } from '../FormLabel/FormLabel'; import { Input } from '../Forms/Legacy/Input/Input'; import { Switch } from '../Forms/Legacy/Switch/Switch'; import { Icon } from '../Icon/Icon'; -import { FormField } from '../FormField/FormField'; -import { InlineFormLabel } from '../FormLabel/FormLabel'; +import { Select } from '../Select/Select'; import { TagsInput } from '../TagsInput/TagsInput'; + +import { BasicAuthSettings } from './BasicAuthSettings'; +import { CustomHeadersSettings } from './CustomHeadersSettings'; +import { HttpProxySettings } from './HttpProxySettings'; import { SigV4AuthSettings } from './SigV4AuthSettings'; -import { useTheme } from '../../themes'; +import { TLSAuthSettings } from './TLSAuthSettings'; import { HttpSettingsProps } from './types'; const ACCESS_OPTIONS: Array> = [ diff --git a/packages/grafana-ui/src/components/DataSourceSettings/HttpProxySettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/HttpProxySettings.tsx index 4441672d276..07ab46edec3 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/HttpProxySettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/HttpProxySettings.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { HttpSettingsBaseProps } from './types'; + import { Switch } from '../Forms/Legacy/Switch/Switch'; +import { HttpSettingsBaseProps } from './types'; + export const HttpProxySettings: React.FC = ({ dataSourceConfig, onChange, diff --git a/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx index d7ce2687278..965721d9fc1 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/SigV4AuthSettings.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import { HttpSettingsBaseProps } from './types'; -import { DataSourceSettings } from '@grafana/data'; + import { AwsAuthDataSourceSecureJsonData, AwsAuthDataSourceJsonData, ConnectionConfig, ConnectionConfigProps, } from '@grafana/aws-sdk'; +import { DataSourceSettings } from '@grafana/data'; + +import { HttpSettingsBaseProps } from './types'; export const SigV4AuthSettings: React.FC = (props) => { const { dataSourceConfig, onChange } = props; diff --git a/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx index 73b42b0206c..9c29706f0c0 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/TLSAuthSettings.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import { KeyValue } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { Tooltip } from '../Tooltip/Tooltip'; +import React from 'react'; + +import { KeyValue } from '@grafana/data'; + +import { FormField } from '../FormField/FormField'; import { Icon } from '../Icon/Icon'; +import { Tooltip } from '../Tooltip/Tooltip'; + import { CertificationKey } from './CertificationKey'; import { HttpSettingsBaseProps } from './types'; -import { FormField } from '../FormField/FormField'; export const TLSAuthSettings: React.FC = ({ dataSourceConfig, onChange }) => { const hasTLSCACert = dataSourceConfig.secureJsonFields && dataSourceConfig.secureJsonFields.tlsCACert; diff --git a/packages/grafana-ui/src/components/DataSourceSettings/types.ts b/packages/grafana-ui/src/components/DataSourceSettings/types.ts index ae71ad191d5..c5c2cc4789a 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/types.ts +++ b/packages/grafana-ui/src/components/DataSourceSettings/types.ts @@ -1,4 +1,5 @@ import React from 'react'; + import { DataSourceSettings } from '@grafana/data'; export interface AzureAuthSettings { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.story.tsx index be64460e183..eca86234fb9 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.story.tsx @@ -1,8 +1,10 @@ import React, { useState } from 'react'; -import { DatePicker } from './DatePicker'; -import { Button } from '../../Button/Button'; -import mdx from './DatePicker.mdx'; + import { withCenteredStory } from '../../../utils/storybook/withCenteredStory'; +import { Button } from '../../Button/Button'; + +import { DatePicker } from './DatePicker'; +import mdx from './DatePicker.mdx'; export default { title: 'Pickers and Editors/TimePickers/Pickers And Editors/DatePicker', diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx index 61b56adfcd9..7ee7671bcc8 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { DatePicker } from './DatePicker'; describe('DatePicker', () => { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx index 7fdc9dba1a0..8e2397df452 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx @@ -1,7 +1,9 @@ +import { css } from '@emotion/css'; import React, { memo } from 'react'; import Calendar from 'react-calendar'; -import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../../themes'; import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper'; import { Icon } from '../../Icon/Icon'; diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.story.tsx index 4fe461cc117..2ccd0ee3121 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.story.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; -import { DatePickerWithInput } from './DatePickerWithInput'; + import { withCenteredStory } from '../../../utils/storybook/withCenteredStory'; + +import { DatePickerWithInput } from './DatePickerWithInput'; import mdx from './DatePickerWithInput.mdx'; export default { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx index e80823e91f9..f67bedfd45c 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { DatePickerWithInput } from './DatePickerWithInput'; +import React from 'react'; + import { dateTimeFormat } from '@grafana/data'; +import { DatePickerWithInput } from './DatePickerWithInput'; + describe('DatePickerWithInput', () => { it('renders date input', () => { render(); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx index ea76baa8bab..ac924d7be24 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx @@ -1,9 +1,11 @@ -import React, { ChangeEvent } from 'react'; import { css } from '@emotion/css'; +import React, { ChangeEvent } from 'react'; + import { dateTime } from '@grafana/data'; -import { DatePicker } from '../DatePicker/DatePicker'; -import { Props as InputProps, Input } from '../../Input/Input'; + import { useStyles } from '../../../themes'; +import { Props as InputProps, Input } from '../../Input/Input'; +import { DatePicker } from '../DatePicker/DatePicker'; export const formatDate = (date: Date | string) => dateTime(date).format('L'); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx index 0e005d42a6d..ac6a6500eeb 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.story.tsx @@ -1,8 +1,11 @@ -import React, { useState } from 'react'; import { Story, Meta } from '@storybook/react'; +import React, { useState } from 'react'; + import { dateTime, DateTime } from '@grafana/data'; -import { DateTimePicker, Props } from './DateTimePicker'; + import { withCenteredStory } from '../../../utils/storybook/withCenteredStory'; + +import { DateTimePicker, Props } from './DateTimePicker'; import mdx from './DateTimePicker.mdx'; export default { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx index 526abef54fb..15e65ffe738 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { dateTime } from '@grafana/data'; + import { DateTimePicker, Props } from './DateTimePicker'; const renderDatetimePicker = (props?: Props) => { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index e375170e4b8..b94b60fa7f4 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -1,14 +1,16 @@ -import React, { FC, FormEvent, ReactNode, useCallback, useEffect, useState } from 'react'; -import { useMedia } from 'react-use'; -import Calendar from 'react-calendar'; import { css, cx } from '@emotion/css'; +import React, { FC, FormEvent, ReactNode, useCallback, useEffect, useState } from 'react'; +import Calendar from 'react-calendar'; +import { useMedia } from 'react-use'; + import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@grafana/data'; + import { Button, ClickOutsideWrapper, HorizontalGroup, Icon, InlineField, Input, Portal } from '../..'; -import { TimeOfDayPicker } from '../TimeOfDayPicker'; -import { getStyles as getCalendarStyles } from '../TimeRangePicker/TimePickerCalendar'; import { useStyles2, useTheme2 } from '../../../themes'; -import { isValid } from '../utils'; +import { TimeOfDayPicker } from '../TimeOfDayPicker'; import { getBodyStyles } from '../TimeRangePicker/CalendarBody'; +import { getStyles as getCalendarStyles } from '../TimeRangePicker/TimePickerCalendar'; +import { isValid } from '../utils'; export interface Props { /** Input date for the component */ diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx index dadccfb4865..c726f5f86c6 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; + import { UseState } from '../../../utils/storybook/UseState'; import { withCenteredStory } from '../../../utils/storybook/withCenteredStory'; + import { RelativeTimeRangePicker } from './RelativeTimeRangePicker'; export default { diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx index 5f29941476e..635d906a6f0 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx @@ -1,8 +1,10 @@ -import React, { useState } from 'react'; import { render, fireEvent, RenderResult } from '@testing-library/react'; -import { RelativeTimeRangePicker } from './RelativeTimeRangePicker'; +import React, { useState } from 'react'; + import { RelativeTimeRange } from '@grafana/data'; +import { RelativeTimeRangePicker } from './RelativeTimeRangePicker'; + function setup(initial: RelativeTimeRange = { from: 900, to: 0 }): RenderResult { const StatefulPicker: React.FC<{}> = () => { const [value, setValue] = useState(initial); diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index 02f7945ceb5..26a97a28b48 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -1,13 +1,20 @@ -import React, { FormEvent, ReactElement, useCallback, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FormEvent, ReactElement, useCallback, useState } from 'react'; + import { RelativeTimeRange, GrafanaTheme2, TimeOption } from '@grafana/data'; + import { useStyles2 } from '../../../themes'; import { Button } from '../../Button'; import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper'; +import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar'; +import { Field } from '../../Forms/Field'; +import { Icon } from '../../Icon/Icon'; +import { getInputStyles, Input } from '../../Input/Input'; +import { Tooltip } from '../../Tooltip/Tooltip'; +import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle'; import { TimeRangeList } from '../TimeRangePicker/TimeRangeList'; import { quickOptions } from '../options'; -import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar'; -import { TimePickerTitle } from '../TimeRangePicker/TimePickerTitle'; + import { isRangeValid, isRelativeFormat, @@ -15,10 +22,6 @@ import { mapRelativeTimeRangeToOption, RangeValidation, } from './utils'; -import { Field } from '../../Forms/Field'; -import { getInputStyles, Input } from '../../Input/Input'; -import { Icon } from '../../Icon/Icon'; -import { Tooltip } from '../../Tooltip/Tooltip'; /** * @internal diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx index 70d201883ab..72d768b27b1 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.story.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; +import { dateTime } from '@grafana/data'; import { TimeOfDayPicker } from '@grafana/ui'; + import { UseState } from '../../utils/storybook/UseState'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { dateTime } from '@grafana/data'; export default { title: 'Pickers and Editors/TimePickers/TimeOfDayPicker', diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx index 673426766c6..6d5bc4a2732 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx @@ -1,12 +1,14 @@ -import React, { FC } from 'react'; -import RcTimePicker from 'rc-time-picker'; import { css, cx } from '@emotion/css'; +import RcTimePicker from 'rc-time-picker'; +import React, { FC } from 'react'; + import { dateTime, DateTime, dateTimeAsMoment, GrafanaTheme } from '@grafana/data'; + import { Icon, useStyles } from '../../index'; import { stylesFactory } from '../../themes'; +import { focusCss } from '../../themes/mixins'; import { inputSizes } from '../Forms/commonStyles'; import { FormInputSize } from '../Forms/types'; -import { focusCss } from '../../themes/mixins'; export interface Props { onChange: (value: DateTime) => void; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.story.tsx index 419c25cc564..36706e2ad7b 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.story.tsx @@ -1,10 +1,13 @@ +import { action } from '@storybook/addon-actions'; import { Story } from '@storybook/react'; import React from 'react'; -import { action } from '@storybook/addon-actions'; + import { dateTime, DefaultTimeZone, TimeRange, TimeZone } from '@grafana/data'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { UseState } from '../../utils/storybook/UseState'; import { TimeRangeInput } from '@grafana/ui'; + +import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { TimeRangeInputProps } from './TimeRangeInput'; import mdx from './TimeRangeInput.mdx'; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx index f946c588b25..f934ed4a7eb 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx @@ -1,15 +1,18 @@ -import React, { FC, FormEvent, MouseEvent, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, FormEvent, MouseEvent, useState } from 'react'; + import { dateMath, dateTime, getDefaultTimeRange, GrafanaTheme2, TimeRange, TimeZone } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { stylesFactory } from '../../themes'; import { useTheme2 } from '../../themes/ThemeContext'; import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper'; import { Icon } from '../Icon/Icon'; import { getInputStyles } from '../Input/Input'; + import { TimePickerButtonLabel } from './TimeRangePicker'; import { TimePickerContent } from './TimeRangePicker/TimePickerContent'; import { quickOptions } from './options'; -import { selectors } from '@grafana/e2e-selectors'; -import { stylesFactory } from '../../themes'; const isValidTimeRange = (range: any) => { return dateMath.isValid(range.from) && dateMath.isValid(range.to); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx index a08fd639a8c..7ca94e6aab2 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.story.tsx @@ -1,15 +1,17 @@ +import { action } from '@storybook/addon-actions'; import { Story } from '@storybook/react'; import React from 'react'; -import { action } from '@storybook/addon-actions'; +import { dateTime, TimeRange, DefaultTimeZone, TimeZone, isDateTime } from '@grafana/data'; import { Button, TimeRangePicker } from '@grafana/ui'; + +import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; import { UseState } from '../../utils/storybook/UseState'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { dateTime, TimeRange, DefaultTimeZone, TimeZone, isDateTime } from '@grafana/data'; -import { TimeRangePickerProps } from './TimeRangePicker'; -import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; +import { TimeRangePickerProps } from './TimeRangePicker'; + export default { title: 'Pickers and Editors/TimePickers/TimeRangePicker', component: TimeRangePicker, diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx index 12aeb15caa2..f3ac08567ab 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx @@ -1,6 +1,8 @@ -import { createTheme, dateTime, TimeRange } from '@grafana/data'; import { render } from '@testing-library/react'; import React from 'react'; + +import { createTheme, dateTime, TimeRange } from '@grafana/data'; + import { UnthemedTimeRangePicker } from './TimeRangePicker'; const from = dateTime('2019-12-17T07:48:27.433Z'); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx index 167cc9d4e6a..375650c9fa3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx @@ -1,16 +1,9 @@ -// Libraries -import React, { memo, FormEvent, createRef, useState, ReactElement } from 'react'; import { css } from '@emotion/css'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { useOverlay } from '@react-aria/overlays'; +import React, { memo, FormEvent, createRef, useState, ReactElement } from 'react'; -// Components -import { Tooltip } from '../Tooltip/Tooltip'; -import { TimePickerContent } from './TimeRangePicker/TimePickerContent'; - -// Utils & Services -import { stylesFactory } from '../../themes/stylesFactory'; -import { withTheme, useTheme } from '../../themes/ThemeContext'; - -// Types import { isDateTime, rangeUtil, @@ -21,13 +14,16 @@ import { TimeZone, dateMath, } from '@grafana/data'; -import { Themeable } from '../../types'; -import { quickOptions } from './options'; -import { ButtonGroup, ToolbarButton } from '../Button'; import { selectors } from '@grafana/e2e-selectors'; -import { useDialog } from '@react-aria/dialog'; -import { useOverlay } from '@react-aria/overlays'; -import { FocusScope } from '@react-aria/focus'; + +import { withTheme, useTheme } from '../../themes/ThemeContext'; +import { stylesFactory } from '../../themes/stylesFactory'; +import { Themeable } from '../../types'; +import { ButtonGroup, ToolbarButton } from '../Button'; +import { Tooltip } from '../Tooltip/Tooltip'; + +import { TimePickerContent } from './TimeRangePicker/TimePickerContent'; +import { quickOptions } from './options'; /** @public */ export interface TimeRangePickerProps extends Themeable { @@ -45,6 +41,7 @@ export interface TimeRangePickerProps extends Themeable { onZoom: () => void; history?: TimeRange[]; hideQuickRanges?: boolean; + widthOverride?: number; } export interface State { @@ -68,6 +65,7 @@ export function UnthemedTimeRangePicker(props: TimeRangePickerProps): ReactEleme onChangeTimeZone, onChangeFiscalYearStartMonth, hideQuickRanges, + widthOverride, } = props; const onChange = (timeRange: TimeRange) => { @@ -129,6 +127,7 @@ export function UnthemedTimeRangePicker(props: TimeRangePickerProps): ReactEleme quickOptions={quickOptions} history={history} showHistory + widthOverride={widthOverride} onChangeTimeZone={onChangeTimeZone} onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth} hideQuickRanges={hideQuickRanges} diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx index d561b68f31d..fdc684547d3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx @@ -1,11 +1,14 @@ -import React, { useCallback } from 'react'; -import { useStyles2 } from '../../../themes'; -import Calendar from 'react-calendar'; import { css } from '@emotion/css'; -import { Icon } from '../../Icon/Icon'; -import { TimePickerCalendarProps } from './TimePickerCalendar'; +import React, { useCallback } from 'react'; +import Calendar from 'react-calendar'; + import { GrafanaTheme2, dateTime, dateTimeParse, DateTime, TimeZone } from '@grafana/data'; +import { useStyles2 } from '../../../themes'; +import { Icon } from '../../Icon/Icon'; + +import { TimePickerCalendarProps } from './TimePickerCalendar'; + export function Body({ onChange, from, to, timeZone }: TimePickerCalendarProps) { const value = inputToValue(from, to); const onCalendarChange = useOnCalendarChange(onChange, timeZone); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarFooter.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarFooter.tsx index e12dfbda9e8..59d54f6020e 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarFooter.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarFooter.tsx @@ -1,8 +1,11 @@ +import { css } from '@emotion/css'; import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../../themes'; import { Button } from '../../Button'; -import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; + import { TimePickerCalendarProps } from './TimePickerCalendar'; export function Footer({ onClose, onApply }: TimePickerCalendarProps) { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarHeader.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarHeader.tsx index 87ac740e560..e8ea6d9a0cf 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarHeader.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarHeader.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import { TimePickerTitle } from './TimePickerTitle'; -import { Button } from '../../Button'; -import { selectors } from '@grafana/e2e-selectors'; -import { TimePickerCalendarProps } from './TimePickerCalendar'; -import { useStyles2 } from '../../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { useStyles2 } from '../../../themes'; +import { Button } from '../../Button'; + +import { TimePickerCalendarProps } from './TimePickerCalendar'; +import { TimePickerTitle } from './TimePickerTitle'; export function Header({ onClose }: TimePickerCalendarProps) { const styles = useStyles2(getHeaderStyles); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerCalendar.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerCalendar.tsx index 1f0e4c0e3ac..826707a4cbc 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerCalendar.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerCalendar.tsx @@ -1,14 +1,17 @@ -import React, { FormEvent, memo } from 'react'; import { css } from '@emotion/css'; -import { DateTime, GrafanaTheme2, TimeZone } from '@grafana/data'; -import { useTheme2 } from '../../../themes'; -import { Header } from './CalendarHeader'; -import { selectors } from '@grafana/e2e-selectors'; import { useDialog } from '@react-aria/dialog'; import { FocusScope } from '@react-aria/focus'; import { OverlayContainer, useOverlay } from '@react-aria/overlays'; +import React, { FormEvent, memo } from 'react'; + +import { DateTime, GrafanaTheme2, TimeZone } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { useTheme2 } from '../../../themes'; + import { Body } from './CalendarBody'; import { Footer } from './CalendarFooter'; +import { Header } from './CalendarHeader'; export const getStyles = (theme: GrafanaTheme2, isReversed = false) => { return { @@ -37,7 +40,6 @@ export const getStyles = (theme: GrafanaTheme2, isReversed = false) => { modal: css` position: fixed; top: 20%; - left: 25%; width: 100%; z-index: ${theme.zIndex.modal}; `, diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx index 07d0fafb57b..40d9dc2b693 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx @@ -1,6 +1,8 @@ -import { dateTime, TimeRange } from '@grafana/data'; import { render, RenderResult, screen } from '@testing-library/react'; import React from 'react'; + +import { dateTime, TimeRange } from '@grafana/data'; + import { PropsWithScreenSize, TimePickerContentWithScreenSize } from './TimePickerContent'; describe('TimePickerContent', () => { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx index 244578d35fe..2a7ca635dc3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.tsx @@ -1,18 +1,20 @@ -import { GrafanaTheme2, isDateTime, rangeUtil, RawTimeRange, TimeOption, TimeRange, TimeZone } from '@grafana/data'; import { css, cx } from '@emotion/css'; import React, { memo, useMemo, useState } from 'react'; -import { useMedia } from 'react-use'; + +import { GrafanaTheme2, isDateTime, rangeUtil, RawTimeRange, TimeOption, TimeRange, TimeZone } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { FilterInput } from '../..'; import { stylesFactory, useTheme2 } from '../../../themes'; +import { getFocusStyles } from '../../../themes/mixins'; import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar'; import { Icon } from '../../Icon/Icon'; -import { mapOptionToTimeRange, mapRangeToTimeOption } from './mapper'; + +import { TimePickerFooter } from './TimePickerFooter'; import { TimePickerTitle } from './TimePickerTitle'; import { TimeRangeForm } from './TimeRangeForm'; import { TimeRangeList } from './TimeRangeList'; -import { TimePickerFooter } from './TimePickerFooter'; -import { getFocusStyles } from '../../../themes/mixins'; -import { selectors } from '@grafana/e2e-selectors'; -import { FilterInput } from '../..'; +import { mapOptionToTimeRange, mapRangeToTimeOption } from './mapper'; interface Props { value: TimeRange; @@ -29,6 +31,7 @@ interface Props { /** Reverse the order of relative and absolute range pickers. Used to left align the picker in forms */ isReversed?: boolean; hideQuickRanges?: boolean; + widthOverride?: number; } export interface PropsWithScreenSize extends Props { @@ -60,7 +63,7 @@ export const TimePickerContentWithScreenSize: React.FC = (p const isContainerTall = (isFullscreen && showHistory) || (!isFullscreen && ((showHistory && !isHistoryEmpty) || !hideQuickRanges)); const theme = useTheme2(); - const styles = getStyles(theme, isReversed, hideQuickRanges, isContainerTall); + const styles = getStyles(theme, isReversed, hideQuickRanges, isContainerTall, isFullscreen); const historyOptions = mapToHistoryOptions(history, timeZone); const timeOption = useTimeOption(value.raw, quickOptions); const [searchTerm, setSearchQuery] = useState(''); @@ -112,9 +115,9 @@ export const TimePickerContentWithScreenSize: React.FC = (p }; export const TimePickerContent: React.FC = (props) => { + const { widthOverride } = props; const theme = useTheme2(); - const isFullscreen = useMedia(`(min-width: ${theme.breakpoints.values.lg}px)`); - + const isFullscreen = (widthOverride || window.innerWidth) >= theme.breakpoints.values.lg; return ; }; @@ -251,22 +254,18 @@ const useTimeOption = (raw: RawTimeRange, quickOptions: TimeOption[]): TimeOptio }, [raw, quickOptions]); }; -const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRanges, isContainerTall) => { +const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRanges, isContainerTall, isFullscreen) => { return { container: css` background: ${theme.colors.background.primary}; box-shadow: ${theme.shadows.z3}; position: absolute; z-index: ${theme.zIndex.dropdown}; - width: 546px; + width: ${isFullscreen ? '546px' : '262px'}; top: 116%; border-radius: 2px; border: 1px solid ${theme.colors.border.weak}; ${isReversed ? 'left' : 'right'}: 0; - - @media only screen and (max-width: ${theme.breakpoints.values.lg}px) { - width: 262px; - } `, body: css` display: flex; @@ -282,13 +281,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRang order: ${isReversed ? 1 : 0}; `, rightSide: css` - width: 40% !important; + width: ${isFullscreen ? '40%' : '100%'}; !important; border-right: ${isReversed ? `1px solid ${theme.colors.border.weak}` : 'none'}; display: flex; flex-direction: column; - @media only screen and (max-width: ${theme.breakpoints.values.lg}px) { - width: 100% !important; - } `, timeRangeFilter: css` padding: ${theme.spacing(1)}; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx index 1f605ee5629..faead588eb3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerFooter.tsx @@ -1,15 +1,17 @@ -import React, { FC, useCallback, useState } from 'react'; import { css, cx } from '@emotion/css'; +import { isString } from 'lodash'; +import React, { FC, useCallback, useState } from 'react'; + import { getTimeZoneInfo, GrafanaTheme2, TimeZone } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { Field, RadioButtonGroup, Select } from '../..'; import { stylesFactory, useTheme2 } from '../../../themes'; -import { TimeZoneTitle } from '../TimeZonePicker/TimeZoneTitle'; -import { TimeZoneDescription } from '../TimeZonePicker/TimeZoneDescription'; -import { TimeZoneOffset } from '../TimeZonePicker/TimeZoneOffset'; import { Button } from '../../Button'; import { TimeZonePicker } from '../TimeZonePicker'; -import { isString } from 'lodash'; -import { selectors } from '@grafana/e2e-selectors'; -import { Field, RadioButtonGroup, Select } from '../..'; +import { TimeZoneDescription } from '../TimeZonePicker/TimeZoneDescription'; +import { TimeZoneOffset } from '../TimeZonePicker/TimeZoneOffset'; +import { TimeZoneTitle } from '../TimeZonePicker/TimeZoneTitle'; import { monthOptions } from '../options'; interface Props { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerTitle.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerTitle.tsx index 1fa12216e7b..20bf4d120f3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerTitle.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerTitle.tsx @@ -1,6 +1,8 @@ -import React, { memo, PropsWithChildren } from 'react'; import { css } from '@emotion/css'; +import React, { memo, PropsWithChildren } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { useTheme, stylesFactory } from '../../../themes'; const getStyle = stylesFactory((theme: GrafanaTheme) => { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.test.tsx index 57c6cfab340..d67f44b2ec0 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { fireEvent, render, RenderResult } from '@testing-library/react'; +import React from 'react'; + import { dateTimeParse, TimeRange } from '@grafana/data'; -import { TimeRangeForm } from './TimeRangeForm'; import { selectors } from '@grafana/e2e-selectors'; +import { TimeRangeForm } from './TimeRangeForm'; + type TimeRangeFormRenderResult = RenderResult & { getCalendarDayByLabelText(label: string): HTMLButtonElement; }; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.tsx index 904a26f760b..60c1584af2a 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeForm.tsx @@ -1,4 +1,6 @@ import { css } from '@emotion/css'; +import React, { FormEvent, useCallback, useEffect, useState } from 'react'; + import { dateMath, DateTime, @@ -12,12 +14,13 @@ import { TimeZone, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import React, { FormEvent, useCallback, useEffect, useState } from 'react'; + import { Icon, Tooltip } from '../..'; import { useStyles2 } from '../../..'; import { Button } from '../../Button'; import { Field } from '../../Forms/Field'; import { Input } from '../../Input/Input'; + import TimePickerCalendar from './TimePickerCalendar'; interface Props { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeList.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeList.tsx index f6ad8eed76e..71d2cf2d26b 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeList.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeList.tsx @@ -1,9 +1,12 @@ -import React, { ReactNode } from 'react'; import { css } from '@emotion/css'; +import React, { ReactNode } from 'react'; + import { TimeOption } from '@grafana/data'; + +import { stylesFactory } from '../../../themes'; + import { TimePickerTitle } from './TimePickerTitle'; import { TimeRangeOption } from './TimeRangeOption'; -import { stylesFactory } from '../../../themes'; const getStyles = stylesFactory(() => { return { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeOption.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeOption.tsx index 14627ea9589..5a9d11c8e9a 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeOption.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeOption.tsx @@ -1,9 +1,11 @@ -import React, { memo } from 'react'; import { css, cx } from '@emotion/css'; +import React, { memo } from 'react'; +import { v4 as uuidv4 } from 'uuid'; + import { GrafanaTheme2, TimeOption } from '@grafana/data'; + import { useStyles2 } from '../../../themes/ThemeContext'; import { getFocusStyles } from '../../../themes/mixins'; -import { v4 as uuidv4 } from 'uuid'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx index 56dd301bdc6..b476337e708 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.story.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; import { TimeZonePicker } from '@grafana/ui'; + import { UseState } from '../../utils/storybook/UseState'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx index badc806f9d7..8fc52be3cef 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx @@ -1,5 +1,6 @@ -import React, { useMemo, useCallback } from 'react'; import { toLower, isEmpty, isString } from 'lodash'; +import React, { useMemo, useCallback } from 'react'; + import { SelectableValue, getTimeZoneInfo, @@ -9,10 +10,12 @@ import { TimeZone, InternalTimeZones, } from '@grafana/data'; + import { Select } from '../Select/Select'; -import { CompactTimeZoneOption, WideTimeZoneOption, SelectableZone } from './TimeZonePicker/TimeZoneOption'; + import { TimeZoneGroup } from './TimeZonePicker/TimeZoneGroup'; import { formatUtcOffset } from './TimeZonePicker/TimeZoneOffset'; +import { CompactTimeZoneOption, WideTimeZoneOption, SelectableZone } from './TimeZonePicker/TimeZoneOption'; export interface Props { onChange: (timeZone?: TimeZone) => void; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneDescription.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneDescription.tsx index 8d4a173ed52..fbfb016ccdf 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneDescription.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneDescription.tsx @@ -1,6 +1,8 @@ -import React, { PropsWithChildren, useMemo } from 'react'; import { css } from '@emotion/css'; +import React, { PropsWithChildren, useMemo } from 'react'; + import { GrafanaTheme, TimeZoneInfo } from '@grafana/data'; + import { useTheme, stylesFactory } from '../../../themes'; interface Props { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneGroup.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneGroup.tsx index 68060b2d14a..faa416d6afa 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneGroup.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneGroup.tsx @@ -1,6 +1,8 @@ -import React, { PropsWithChildren } from 'react'; import { css } from '@emotion/css'; +import React, { PropsWithChildren } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { useTheme, stylesFactory } from '../../../themes'; interface Props { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOffset.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOffset.tsx index 742a5cdd65f..5904f99899c 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOffset.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOffset.tsx @@ -1,8 +1,10 @@ -import React, { PropsWithChildren } from 'react'; import { css, cx } from '@emotion/css'; -import { GrafanaTheme, TimeZone, dateTimeFormat } from '@grafana/data'; -import { useTheme, stylesFactory } from '../../../themes'; import { isString } from 'lodash'; +import React, { PropsWithChildren } from 'react'; + +import { GrafanaTheme, TimeZone, dateTimeFormat } from '@grafana/data'; + +import { useTheme, stylesFactory } from '../../../themes'; interface Props { timestamp: number; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOption.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOption.tsx index cfe89819af9..3c8807dfea0 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOption.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneOption.tsx @@ -1,13 +1,16 @@ -import React, { PropsWithChildren } from 'react'; import { css, cx } from '@emotion/css'; +import { isString } from 'lodash'; +import React, { PropsWithChildren } from 'react'; + import { GrafanaTheme2, SelectableValue, getTimeZoneInfo } from '@grafana/data'; + import { useTheme2 } from '../../../themes/ThemeContext'; import { stylesFactory } from '../../../themes/stylesFactory'; import { Icon } from '../../Icon/Icon'; -import { TimeZoneOffset } from './TimeZoneOffset'; + import { TimeZoneDescription } from './TimeZoneDescription'; +import { TimeZoneOffset } from './TimeZoneOffset'; import { TimeZoneTitle } from './TimeZoneTitle'; -import { isString } from 'lodash'; interface Props { isFocused: boolean; diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneTitle.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneTitle.tsx index 7fb95bdc381..9194e45de76 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneTitle.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker/TimeZoneTitle.tsx @@ -1,6 +1,8 @@ -import React, { ReactNode } from 'react'; import { css } from '@emotion/css'; +import React, { ReactNode } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { useTheme, stylesFactory } from '../../../themes'; interface Props { diff --git a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx index 85742db207e..e42e3c87c02 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.story.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; import { WeekStartPicker } from '@grafana/ui'; + import { UseState } from '../../utils/storybook/UseState'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; diff --git a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx index 858d42e6c96..72962bd258d 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx @@ -1,8 +1,10 @@ import React, { useCallback } from 'react'; + import { SelectableValue } from '@grafana/data'; -import { Select } from '../Select/Select'; import { selectors } from '@grafana/e2e-selectors'; +import { Select } from '../Select/Select'; + export interface Props { onChange: (weekStart: string) => void; value: string; diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx index db92c2864b3..fb491d5c8c1 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.story.tsx @@ -1,10 +1,13 @@ -import React, { useState } from 'react'; import { Story } from '@storybook/react'; +import React, { useState } from 'react'; + import { Button, Drawer, Tab, TabsBar } from '@grafana/ui'; + import { UseState } from '../../utils/storybook/UseState'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import mdx from './Drawer.mdx'; + import { Props } from './Drawer'; +import mdx from './Drawer.mdx'; export default { title: 'Overlays/Drawer', diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index 1f515190742..7bf729d0334 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -1,15 +1,16 @@ -import React, { CSSProperties, ReactNode, useState, useEffect } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; -import RcDrawer from 'rc-drawer'; import { css } from '@emotion/css'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { useOverlay } from '@react-aria/overlays'; +import RcDrawer from 'rc-drawer'; +import React, { CSSProperties, ReactNode, useState, useEffect } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { useStyles2 } from '../../themes'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; import { IconButton } from '../IconButton/IconButton'; -import { useStyles2 } from '../../themes'; -import { FocusScope } from '@react-aria/focus'; -import { useDialog } from '@react-aria/dialog'; -import { useOverlay } from '@react-aria/overlays'; export interface Props { children: ReactNode; diff --git a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.story.internal.tsx b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.story.internal.tsx index e2737ec0eb8..0fda4e017c9 100644 --- a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.story.internal.tsx +++ b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.story.internal.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; import { Meta, Story } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { UseState } from '../../utils/storybook/UseState'; +import React from 'react'; + import { SelectableValue } from '@grafana/data'; -import { ButtonSelect } from './ButtonSelect'; + import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; +import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { ButtonSelect } from './ButtonSelect'; export default { title: 'Forms/Select/ButtonSelect', diff --git a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx index c078b23ba1d..be0ae91df41 100644 --- a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx @@ -1,16 +1,18 @@ +import { css } from '@emotion/css'; +import { useButton } from '@react-aria/button'; +import { FocusScope } from '@react-aria/focus'; +import { useMenuTrigger } from '@react-aria/menu'; +import { useMenuTriggerState } from '@react-stately/menu'; import React, { HTMLAttributes } from 'react'; -import { PopoverContent } from '../Tooltip'; + import { GrafanaTheme2, SelectableValue } from '@grafana/data'; + +import { useStyles2 } from '../../themes/ThemeContext'; import { ToolbarButtonVariant, ToolbarButton, ButtonGroup } from '../Button'; import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper'; -import { css } from '@emotion/css'; -import { useStyles2 } from '../../themes/ThemeContext'; import { Menu } from '../Menu/Menu'; import { MenuItem } from '../Menu/MenuItem'; -import { FocusScope } from '@react-aria/focus'; -import { useMenuTriggerState } from '@react-stately/menu'; -import { useMenuTrigger } from '@react-aria/menu'; -import { useButton } from '@react-aria/button'; +import { PopoverContent } from '../Tooltip'; export interface Props extends HTMLAttributes { className?: string; diff --git a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.story.tsx b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.story.tsx index dc57b6fb9b3..90e18b2c0ac 100644 --- a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.story.tsx +++ b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.story.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { EmptySearchResult } from './EmptySearchResult'; + import { withCenteredStory } from '@grafana/ui/src/utils/storybook/withCenteredStory'; + +import { EmptySearchResult } from './EmptySearchResult'; import mdx from './EmptySearchResult.mdx'; export default { diff --git a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx index 6c1490351f0..a340d7154b0 100644 --- a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx +++ b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx @@ -1,6 +1,8 @@ -import React, { FC } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; export interface Props { diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.story.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.story.tsx index 2231c657fdb..885cd49be2f 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.story.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.story.tsx @@ -1,10 +1,13 @@ import React, { useState } from 'react'; -import { ErrorBoundary, ErrorBoundaryAlert } from './ErrorBoundary'; + import { withCenteredStory } from '@grafana/ui/src/utils/storybook/withCenteredStory'; -import mdx from './ErrorBoundary.mdx'; -import { Button } from '../Button'; -import { ErrorWithStack } from './ErrorWithStack'; + import { Alert } from '../Alert/Alert'; +import { Button } from '../Button'; + +import { ErrorBoundary, ErrorBoundaryAlert } from './ErrorBoundary'; +import mdx from './ErrorBoundary.mdx'; +import { ErrorWithStack } from './ErrorWithStack'; export default { title: 'General/ErrorBoundary', diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx index 925210cfcd7..62d888eccf8 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx @@ -1,7 +1,8 @@ -import React, { FC } from 'react'; -import { render, screen } from '@testing-library/react'; -import { ErrorBoundary } from './ErrorBoundary'; import { captureException } from '@sentry/browser'; +import { render, screen } from '@testing-library/react'; +import React, { FC } from 'react'; + +import { ErrorBoundary } from './ErrorBoundary'; jest.mock('@sentry/browser'); diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx index 6496443c96f..a929bbfec35 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,6 +1,8 @@ -import React, { PureComponent, ReactNode, ComponentType } from 'react'; import { captureException } from '@sentry/browser'; +import React, { PureComponent, ReactNode, ComponentType } from 'react'; + import { Alert } from '../Alert/Alert'; + import { ErrorWithStack } from './ErrorWithStack'; export interface ErrorInfo { diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorWithStack.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorWithStack.tsx index 50708a572c0..4a2cb35f693 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorWithStack.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorWithStack.tsx @@ -1,7 +1,9 @@ -import React, { FunctionComponent } from 'react'; -import { ErrorBoundaryApi } from './ErrorBoundary'; -import { stylesFactory } from '../../themes'; import { css } from '@emotion/css'; +import React, { FunctionComponent } from 'react'; + +import { stylesFactory } from '../../themes'; + +import { ErrorBoundaryApi } from './ErrorBoundary'; const getStyles = stylesFactory(() => { return css` diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx index 9451ddfa1a8..32466894539 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx @@ -1,7 +1,10 @@ -import { FileDropzone, FileDropzoneProps } from '@grafana/ui'; import { Meta, Story } from '@storybook/react'; import React from 'react'; + +import { FileDropzone, FileDropzoneProps } from '@grafana/ui'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './FileDropzone.mdx'; export default { diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx index 2d589f69ef6..3f970c5cd88 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; + import { FileDropzone } from './FileDropzone'; import { REMOVE_FILE } from './FileListItem'; diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx index 5f3f5bf120f..c9daf9daa80 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx @@ -1,10 +1,13 @@ import { css, cx } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; import { uniqueId } from 'lodash'; import React, { ReactNode, useCallback, useState } from 'react'; import { DropEvent, DropzoneOptions, FileRejection, useDropzone } from 'react-dropzone'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { useTheme2 } from '../../themes'; import { Icon } from '../Icon/Icon'; + import { FileListItem } from './FileListItem'; export interface FileDropzoneProps { diff --git a/packages/grafana-ui/src/components/FileDropzone/FileListItem.story.tsx b/packages/grafana-ui/src/components/FileDropzone/FileListItem.story.tsx index af0ae0ce612..654922796e9 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileListItem.story.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileListItem.story.tsx @@ -1,6 +1,8 @@ import { Meta, Story } from '@storybook/react'; import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { FileListItem as FileListItemComponent, FileListItemProps } from './FileListItem'; import mdx from './FileListItem.mdx'; diff --git a/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx b/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx index e1a0e75f2de..0ac8d8536ae 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; + import { FileListItem, REMOVE_FILE } from './FileListItem'; const file = ({ diff --git a/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx b/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx index cf039248391..fba1fb6e4c9 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx @@ -1,11 +1,14 @@ import { css } from '@emotion/css'; -import { formattedValueToString, getValueFormat, GrafanaTheme2 } from '@grafana/data'; import React from 'react'; + +import { formattedValueToString, getValueFormat, GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; import { trimFileName } from '../../utils/file'; import { Button } from '../Button'; import { Icon } from '../Icon/Icon'; import { IconButton } from '../IconButton/IconButton'; + import { DropzoneFile } from './FileDropzone'; export const REMOVE_FILE = 'Remove file'; diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx index ea5a4b975f5..a0d32acb972 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.story.tsx @@ -1,8 +1,11 @@ -import React from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { FileUpload } from '@grafana/ui'; -import mdx from './FileUpload.mdx'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import { FileUpload } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import mdx from './FileUpload.mdx'; export default { title: 'Forms/FileUpload', diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx index eff5555819e..8860c2efa62 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { FileUpload } from './FileUpload'; describe('FileUpload', () => { diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx index 48310576ae8..98fc47e7c5c 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx @@ -1,11 +1,13 @@ -import React, { FC, FormEvent, useCallback, useState } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { Icon } from '../index'; +import React, { FC, FormEvent, useCallback, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { stylesFactory, useTheme2 } from '../../themes'; import { ComponentSize } from '../../types/size'; -import { getButtonStyles } from '../Button'; import { trimFileName } from '../../utils/file'; +import { getButtonStyles } from '../Button'; +import { Icon } from '../index'; export interface Props { /** Callback function to handle uploaded file */ diff --git a/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx b/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx index d394ae335c1..31537f5d131 100644 --- a/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx +++ b/packages/grafana-ui/src/components/FilterInput/FilterInput.tsx @@ -1,5 +1,7 @@ import React, { HTMLProps } from 'react'; + import { escapeStringForRegex, unEscapeStringFromRegex } from '@grafana/data'; + import { Button, Icon, Input } from '..'; import { useCombinedRefs } from '../../utils/useCombinedRefs'; diff --git a/packages/grafana-ui/src/components/FilterPill/FilterPill.story.tsx b/packages/grafana-ui/src/components/FilterPill/FilterPill.story.tsx index d84751db2a8..0a5c007ae5f 100644 --- a/packages/grafana-ui/src/components/FilterPill/FilterPill.story.tsx +++ b/packages/grafana-ui/src/components/FilterPill/FilterPill.story.tsx @@ -1,11 +1,14 @@ -import React, { useState } from 'react'; import { Story } from '@storybook/react'; -import { FilterPill, FilterPillProps } from './FilterPill'; +import React, { useState } from 'react'; + import { withCenteredStory } from '@grafana/ui/src/utils/storybook/withCenteredStory'; -import mdx from './FilterPill.mdx'; + import { getAvailableIcons } from '../../types'; import { HorizontalGroup } from '../Layout/Layout'; +import { FilterPill, FilterPillProps } from './FilterPill'; +import mdx from './FilterPill.mdx'; + export default { title: 'General/FilterPill', component: FilterPill, diff --git a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx index 654f19eb875..c1a36603a06 100644 --- a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx +++ b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { useStyles2 } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { Icon } from '../Icon/Icon'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; import { IconName } from '../../types'; +import { Icon } from '../Icon/Icon'; export interface FilterPillProps { selected: boolean; diff --git a/packages/grafana-ui/src/components/FormField/FormField.story.internal.tsx b/packages/grafana-ui/src/components/FormField/FormField.story.internal.tsx index 7b320bdf72a..5b3fe2a013a 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.story.internal.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.story.internal.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { FormField, Props } from './FormField'; export default { diff --git a/packages/grafana-ui/src/components/FormField/FormField.test.tsx b/packages/grafana-ui/src/components/FormField/FormField.test.tsx index 95ccb57b658..9cd91048024 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.test.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { FormField, Props } from './FormField'; const setup = (propOverrides?: Partial) => { diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx index 1ef3a594610..b15b54c3e1a 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.tsx @@ -1,5 +1,6 @@ -import React, { InputHTMLAttributes, FunctionComponent } from 'react'; import { css, cx } from '@emotion/css'; +import React, { InputHTMLAttributes, FunctionComponent } from 'react'; + import { InlineFormLabel } from '../FormLabel/FormLabel'; import { PopoverContent } from '../Tooltip'; diff --git a/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx b/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx index b80178030ba..ae6c1c46466 100644 --- a/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx +++ b/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx @@ -1,7 +1,8 @@ -import React, { FunctionComponent, ReactNode } from 'react'; import classNames from 'classnames'; -import { Tooltip, PopoverContent } from '../Tooltip'; +import React, { FunctionComponent, ReactNode } from 'react'; + import { Icon } from '../Icon/Icon'; +import { Tooltip, PopoverContent } from '../Tooltip'; interface Props { children: ReactNode; diff --git a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.story.tsx b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.story.tsx index d81bceb6b8b..94f6827be04 100644 --- a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.story.tsx +++ b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.story.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { FormattedValueDisplay } from './FormattedValueDisplay'; + import { withCenteredStory } from '@grafana/ui/src/utils/storybook/withCenteredStory'; + +import { FormattedValueDisplay } from './FormattedValueDisplay'; import mdx from './FormattedValueDisplay.mdx'; export default { diff --git a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx index decb8f2486b..17a074d0f8c 100644 --- a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx +++ b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx @@ -1,4 +1,5 @@ import React, { FC, CSSProperties, HTMLProps } from 'react'; + import { FormattedValue } from '@grafana/data'; export interface Props extends Omit, 'className' | 'value' | 'style'> { diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx index 3f7229f13c1..c80d0a25f20 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx @@ -1,7 +1,9 @@ import React, { useState, useCallback } from 'react'; -import mdx from './Checkbox.mdx'; -import { Checkbox } from './Checkbox'; + import { VerticalGroup } from '../Layout/Layout'; + +import { Checkbox } from './Checkbox'; +import mdx from './Checkbox.mdx'; import { Field } from './Field'; export default { diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index c6af215247e..9fd8b8ff8c8 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -1,10 +1,13 @@ -import React, { HTMLProps, useCallback } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; -import { getLabelStyles } from './Label'; -import { stylesFactory, useStyles2 } from '../../themes'; import { css, cx } from '@emotion/css'; +import React, { HTMLProps, useCallback } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory, useStyles2 } from '../../themes'; import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; +import { getLabelStyles } from './Label'; + export interface CheckboxProps extends Omit, 'value'> { label?: string; description?: string; diff --git a/packages/grafana-ui/src/components/Forms/Field.story.tsx b/packages/grafana-ui/src/components/Forms/Field.story.tsx index de99d13f7d2..859fc811c77 100644 --- a/packages/grafana-ui/src/components/Forms/Field.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.story.tsx @@ -1,7 +1,9 @@ -import React, { useState, useCallback } from 'react'; import { Story } from '@storybook/react'; -import { Field, FieldProps } from './Field'; +import React, { useState, useCallback } from 'react'; + import { Input, Switch } from '..'; + +import { Field, FieldProps } from './Field'; import mdx from './Field.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Forms/Field.test.tsx b/packages/grafana-ui/src/components/Forms/Field.test.tsx index c4e44205bc5..a4fd7e59d26 100644 --- a/packages/grafana-ui/src/components/Forms/Field.test.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { Input } from '../Input/Input'; -import { Field } from './Field'; import { Select } from '../Select/Select'; +import { Field } from './Field'; + describe('Field', () => { it('renders the label', () => { render( diff --git a/packages/grafana-ui/src/components/Forms/Field.tsx b/packages/grafana-ui/src/components/Forms/Field.tsx index 490a0d0f09e..82ab1d5d949 100644 --- a/packages/grafana-ui/src/components/Forms/Field.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.tsx @@ -1,11 +1,14 @@ -import React, { HTMLAttributes } from 'react'; -import { Label } from './Label'; -import { stylesFactory, useTheme2 } from '../../themes'; import { css, cx } from '@emotion/css'; +import React, { HTMLAttributes } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { FieldValidationMessage } from './FieldValidationMessage'; + +import { stylesFactory, useTheme2 } from '../../themes'; import { getChildId } from '../../utils/reactUtils'; +import { FieldValidationMessage } from './FieldValidationMessage'; +import { Label } from './Label'; + export interface FieldProps extends HTMLAttributes { /** Form input element, i.e Input or Switch */ children: React.ReactElement; diff --git a/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx b/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx index 88404a3d08b..6b60b083f46 100644 --- a/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldArray.story.tsx @@ -1,10 +1,13 @@ +import { Meta, Story } from '@storybook/react'; import React from 'react'; + +import { Form, Input, Button, HorizontalGroup } from '@grafana/ui'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; -import { Form, Input, Button, HorizontalGroup } from '@grafana/ui'; + import { FieldArray } from './FieldArray'; import mdx from './FieldArray.mdx'; -import { Meta, Story } from '@storybook/react'; export default { title: 'Forms/FieldArray', diff --git a/packages/grafana-ui/src/components/Forms/FieldArray.tsx b/packages/grafana-ui/src/components/Forms/FieldArray.tsx index 48e4eb7df3c..bab7d3ebae1 100644 --- a/packages/grafana-ui/src/components/Forms/FieldArray.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldArray.tsx @@ -1,5 +1,6 @@ import { FC } from 'react'; import { useFieldArray, UseFieldArrayProps } from 'react-hook-form'; + import { FieldArrayApi } from '../../types'; export interface FieldArrayProps extends UseFieldArrayProps { diff --git a/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx b/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx index 38420169f69..da5856bb665 100644 --- a/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldSet.story.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { Input, Form, FieldSet, Field } from '@grafana/ui'; -import mdx from './FieldSet.mdx'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Button } from '../Button'; +import mdx from './FieldSet.mdx'; + export default { title: 'Forms/FieldSet', component: FieldSet, diff --git a/packages/grafana-ui/src/components/Forms/FieldSet.tsx b/packages/grafana-ui/src/components/Forms/FieldSet.tsx index 0749e9856de..11ab276b3b6 100644 --- a/packages/grafana-ui/src/components/Forms/FieldSet.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldSet.tsx @@ -1,7 +1,10 @@ -import React, { FC, HTMLProps } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, HTMLProps } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + import { stylesFactory, useTheme2 } from '../../themes'; + import { Legend } from './Legend'; export interface Props extends Omit, 'label'> { diff --git a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.story.tsx b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.story.tsx index d5d162062d8..b9fe82c76da 100644 --- a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.story.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.story.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; + import { FieldValidationMessage } from './FieldValidationMessage'; import mdx from './FieldValidationMessage.mdx'; diff --git a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx index 2e551d7b764..975b574f6f4 100644 --- a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { Icon } from '../Icon/Icon'; + import { stylesFactory, useTheme2 } from '../../themes'; +import { Icon } from '../Icon/Icon'; export interface FieldValidationMessageProps { /** Override component style */ diff --git a/packages/grafana-ui/src/components/Forms/Form.story.tsx b/packages/grafana-ui/src/components/Forms/Form.story.tsx index e1ae71daa9b..ce9960dd60f 100644 --- a/packages/grafana-ui/src/components/Forms/Form.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Form.story.tsx @@ -1,6 +1,7 @@ +import { Story } from '@storybook/react'; import React from 'react'; import { ValidateResult } from 'react-hook-form'; -import { Story } from '@storybook/react'; + import { Field, Legend, @@ -14,8 +15,10 @@ import { TextArea, RadioButtonGroup, } from '@grafana/ui'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; + import mdx from './Form.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Forms/Form.tsx b/packages/grafana-ui/src/components/Forms/Form.tsx index b10051597ae..cc722fdcfe2 100644 --- a/packages/grafana-ui/src/components/Forms/Form.tsx +++ b/packages/grafana-ui/src/components/Forms/Form.tsx @@ -1,7 +1,8 @@ +import { css } from '@emotion/css'; import React, { HTMLProps, useEffect } from 'react'; import { useForm, Mode, DeepPartial, UnpackNestedValue, SubmitHandler } from 'react-hook-form'; + import { FormAPI } from '../../types'; -import { css } from '@emotion/css'; interface FormProps extends Omit, 'onSubmit'> { validateOn?: Mode; diff --git a/packages/grafana-ui/src/components/Forms/InlineField.story.tsx b/packages/grafana-ui/src/components/Forms/InlineField.story.tsx index b9357bb6215..6f051006a15 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.story.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineField.story.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; + import { Input } from '../Input/Input'; import { Select } from '../Select/Select'; + import { InlineField } from './InlineField'; import mdx from './InlineField.mdx'; diff --git a/packages/grafana-ui/src/components/Forms/InlineField.test.tsx b/packages/grafana-ui/src/components/Forms/InlineField.test.tsx index 8ad2241d9cc..7730f890f0c 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.test.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineField.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { Input } from '../Input/Input'; -import { InlineField } from './InlineField'; import { Select } from '../Select/Select'; +import { InlineField } from './InlineField'; + describe('InlineField', () => { it('renders the label', () => { render( diff --git a/packages/grafana-ui/src/components/Forms/InlineField.tsx b/packages/grafana-ui/src/components/Forms/InlineField.tsx index ae0f9b9bfb1..c7806e649e7 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineField.tsx @@ -1,12 +1,15 @@ -import React, { FC } from 'react'; import { cx, css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + import { useTheme2 } from '../../themes'; -import { InlineLabel } from './InlineLabel'; -import { PopoverContent } from '../Tooltip'; -import { FieldProps } from './Field'; import { getChildId } from '../../utils/reactUtils'; +import { PopoverContent } from '../Tooltip'; + +import { FieldProps } from './Field'; import { FieldValidationMessage } from './FieldValidationMessage'; +import { InlineLabel } from './InlineLabel'; export interface Props extends Omit { /** Content for the label's tooltip */ diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx index f4c43643a54..3e12be9767a 100644 --- a/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.story.tsx @@ -1,8 +1,10 @@ import React from 'react'; + +import { Input } from '../Input/Input'; + +import { InlineField } from './InlineField'; import { InlineFieldRow } from './InlineFieldRow'; import mdx from './InlineFieldRow.mdx'; -import { InlineField } from './InlineField'; -import { Input } from '../Input/Input'; export default { title: 'Forms/InlineFieldRow', diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx index 0409ef4d326..f9af56470b2 100644 --- a/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx @@ -1,8 +1,10 @@ -import React, { FC, HTMLProps, ReactNode } from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles } from '../../themes'; +import React, { FC, HTMLProps, ReactNode } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { useStyles } from '../../themes'; + export interface Props extends Omit, 'css'> { children: ReactNode | ReactNode[]; } diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx b/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx index 857bbd00fbd..6f491dba9a9 100644 --- a/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.story.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { InlineLabel } from './InlineLabel'; import mdx from './InlineLabel.mdx'; diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx index 7f98ba25684..03f1045c109 100644 --- a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx @@ -1,10 +1,12 @@ import { css, cx } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; import React, { FunctionComponent } from 'react'; +import { GrafanaTheme } from '@grafana/data'; + import { useTheme } from '../../themes'; import { Icon } from '../Icon/Icon'; import { PopoverContent, Tooltip } from '../Tooltip'; + import { LabelProps } from './Label'; export interface Props extends Omit { diff --git a/packages/grafana-ui/src/components/Forms/InlineSegmentGroup.tsx b/packages/grafana-ui/src/components/Forms/InlineSegmentGroup.tsx index b4eabd3b518..aff6abc6a55 100644 --- a/packages/grafana-ui/src/components/Forms/InlineSegmentGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineSegmentGroup.tsx @@ -1,6 +1,8 @@ -import React, { FC } from 'react'; import { cx, css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { useTheme } from '../../themes'; export interface Props { diff --git a/packages/grafana-ui/src/components/Forms/Label.story.tsx b/packages/grafana-ui/src/components/Forms/Label.story.tsx index ac452922229..42ee838e2a0 100644 --- a/packages/grafana-ui/src/components/Forms/Label.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Label.story.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Label } from './Label'; import mdx from './Label.mdx'; diff --git a/packages/grafana-ui/src/components/Forms/Label.tsx b/packages/grafana-ui/src/components/Forms/Label.tsx index 746f1d389db..ca1c6501232 100644 --- a/packages/grafana-ui/src/components/Forms/Label.tsx +++ b/packages/grafana-ui/src/components/Forms/Label.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { useTheme2, stylesFactory } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useTheme2, stylesFactory } from '../../themes'; import { Icon } from '../Icon/Icon'; export interface LabelProps extends React.LabelHTMLAttributes { diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.story.internal.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.story.internal.tsx index d23efafc09e..3adc868b1ca 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.story.internal.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.story.internal.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; -import { zip, fromPairs } from 'lodash'; - -import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory'; -import { Input } from './Input'; import { Meta, Story } from '@storybook/react'; +import { zip, fromPairs } from 'lodash'; +import React, { useState } from 'react'; + import { EventsWithValidation } from '../../../../utils'; +import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory'; + +import { Input } from './Input'; export default { title: 'Forms/Legacy/Input', diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx index da8b30d124b..18f5487b4e0 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx @@ -1,9 +1,11 @@ +import { shallow } from 'enzyme'; import React from 'react'; import renderer from 'react-test-renderer'; -import { shallow } from 'enzyme'; -import { Input } from './Input'; -import { EventsWithValidation } from '../../../../utils'; + import { ValidationEvents } from '../../../../types'; +import { EventsWithValidation } from '../../../../utils'; + +import { Input } from './Input'; const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars'; const testBlurValidation: ValidationEvents = { diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx index 85e28a09442..76742d372c6 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx @@ -1,7 +1,8 @@ -import React, { PureComponent, ChangeEvent } from 'react'; import classNames from 'classnames'; -import { validate, EventsWithValidation, hasValidationEvent } from '../../../../utils'; +import React, { PureComponent, ChangeEvent } from 'react'; + import { ValidationEvents, ValidationRule } from '../../../../types'; +import { validate, EventsWithValidation, hasValidationEvent } from '../../../../utils'; export enum LegacyInputStatus { Invalid = 'invalid', diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/IndicatorsContainer.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/IndicatorsContainer.tsx index d586997b37b..0cfe9a8cca0 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/IndicatorsContainer.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/IndicatorsContainer.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { Icon } from '../../../Icon/Icon'; import { components } from 'react-select'; +import { Icon } from '../../../Icon/Icon'; + export const IndicatorsContainer = (props: any) => { const isOpen = props.selectProps.menuIsOpen; return ( diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx index 077199661ed..f596a7f9c66 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { components, NoticeProps, GroupBase } from 'react-select'; + import { SelectableValue } from '@grafana/data'; export type Props = NoticeProps, boolean, GroupBase>>; diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.story.internal.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.story.internal.tsx index 2f9a66737d0..0c1dccb4102 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.story.internal.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.story.internal.tsx @@ -1,9 +1,12 @@ -import React, { useState, useCallback } from 'react'; import { action } from '@storybook/addon-actions'; import { Meta, Story } from '@storybook/react'; -import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory'; -import { UseState } from '../../../../utils/storybook/UseState'; +import React, { useState, useCallback } from 'react'; + import { SelectableValue } from '@grafana/data'; + +import { UseState } from '../../../../utils/storybook/UseState'; +import { withCenteredStory } from '../../../../utils/storybook/withCenteredStory'; + import { Select, AsyncSelect as AsyncSelectComponent } from './Select'; export default { diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx index a45de860d6c..2baa2101721 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/Select.tsx @@ -2,21 +2,23 @@ import classNames from 'classnames'; import React, { PureComponent } from 'react'; import { default as ReactSelect, components } from 'react-select'; -import Creatable from 'react-select/creatable'; import { default as ReactAsyncSelect } from 'react-select/async'; +import Creatable from 'react-select/creatable'; // Components -import { SelectOption } from './SelectOption'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; + +import { ThemeContext } from '../../../../themes'; +import { CustomScrollbar } from '../../../CustomScrollbar/CustomScrollbar'; import { SelectOptionGroup } from '../../../Select/SelectOptionGroup'; import { SingleValue } from '../../../Select/SingleValue'; +import resetSelectStyles from '../../../Select/resetSelectStyles'; import { SelectCommonProps, SelectAsyncProps } from '../../../Select/types'; +import { Tooltip, PopoverContent } from '../../../Tooltip'; + import IndicatorsContainer from './IndicatorsContainer'; import NoOptionsMessage from './NoOptionsMessage'; -import resetSelectStyles from '../../../Select/resetSelectStyles'; -import { CustomScrollbar } from '../../../CustomScrollbar/CustomScrollbar'; -import { Tooltip, PopoverContent } from '../../../Tooltip'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { ThemeContext } from '../../../../themes'; +import { SelectOption } from './SelectOption'; /** * Changes in new selects: diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.test.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.test.tsx index aa68baed4c6..091601dcd5f 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.test.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import renderer from 'react-test-renderer'; -import SelectOption from './SelectOption'; import { OptionProps } from 'react-select'; +import renderer from 'react-test-renderer'; + +import SelectOption from './SelectOption'; const model: OptionProps = { data: jest.fn(), diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx index b0f49f78f33..ecfa3948e54 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { Icon } from '../../../Icon/Icon'; import { components, OptionProps } from 'react-select'; +import { Icon } from '../../../Icon/Icon'; + // https://github.com/JedWatson/react-select/issues/3038 export interface ExtendedOptionProps extends OptionProps { data: { diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.story.internal.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.story.internal.tsx index 699c8bfbfaf..d6ab701e5e1 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.story.internal.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.story.internal.tsx @@ -1,7 +1,7 @@ +import { Meta, Story } from '@storybook/react'; import React, { useState } from 'react'; import { Props, Switch } from './Switch'; -import { Meta, Story } from '@storybook/react'; export default { title: 'Forms/Legacy/Switch', diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx index 30a380cd470..f35ee957060 100644 --- a/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx @@ -1,8 +1,9 @@ -import React, { PureComponent } from 'react'; -import { uniqueId } from 'lodash'; import { Placement } from '@popperjs/core'; -import { Tooltip } from '../../../Tooltip/Tooltip'; +import { uniqueId } from 'lodash'; +import React, { PureComponent } from 'react'; + import { Icon } from '../../..'; +import { Tooltip } from '../../../Tooltip/Tooltip'; export interface Props { label: string; diff --git a/packages/grafana-ui/src/components/Forms/Legend.story.tsx b/packages/grafana-ui/src/components/Forms/Legend.story.tsx index 75acb2e7574..d7e4fc8e081 100644 --- a/packages/grafana-ui/src/components/Forms/Legend.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Legend.story.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; import { Legend } from '@grafana/ui'; + import mdx from './Legend.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Forms/Legend.tsx b/packages/grafana-ui/src/components/Forms/Legend.tsx index 8e75237c7a1..523792e0869 100644 --- a/packages/grafana-ui/src/components/Forms/Legend.tsx +++ b/packages/grafana-ui/src/components/Forms/Legend.tsx @@ -1,7 +1,9 @@ -import React, { ReactNode } from 'react'; -import { useTheme, stylesFactory } from '../../themes'; -import { GrafanaTheme } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { ReactNode } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; + +import { useTheme, stylesFactory } from '../../themes'; export interface LabelProps extends React.HTMLAttributes { children: string | ReactNode; diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx index c72688619be..7c1d0768e10 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx @@ -1,11 +1,13 @@ -import React from 'react'; -import { useTheme2, stylesFactory } from '../../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; -import { getPropertiesForButtonSize } from '../commonStyles'; -import { getFocusStyles, getMouseFocusStyles } from '../../../themes/mixins'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { StringSelector } from '@grafana/e2e-selectors'; +import { useTheme2, stylesFactory } from '../../../themes'; +import { getFocusStyles, getMouseFocusStyles } from '../../../themes/mixins'; +import { getPropertiesForButtonSize } from '../commonStyles'; + export type RadioButtonSize = 'sm' | 'md'; export interface RadioButtonProps { diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx index 40799595321..3a46d409047 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.story.tsx @@ -1,7 +1,8 @@ -import React, { useState } from 'react'; -import mdx from './RadioButtonGroup.mdx'; -import { RadioButtonGroup } from './RadioButtonGroup'; import { Story } from '@storybook/react'; +import React, { useState } from 'react'; + +import { RadioButtonGroup } from './RadioButtonGroup'; +import mdx from './RadioButtonGroup.mdx'; export default { title: 'Forms/RadioButtonGroup', diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index c817ef4d391..135c0689b98 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -1,11 +1,14 @@ -import React, { useCallback, useEffect, useRef } from 'react'; import { css, cx } from '@emotion/css'; import { uniqueId } from 'lodash'; +import React, { useCallback, useEffect, useRef } from 'react'; + import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { RadioButtonSize, RadioButton } from './RadioButton'; -import { Icon } from '../../Icon/Icon'; -import { IconName } from '../../../types/icon'; + import { useStyles2 } from '../../../themes'; +import { IconName } from '../../../types/icon'; +import { Icon } from '../../Icon/Icon'; + +import { RadioButtonSize, RadioButton } from './RadioButton'; export interface RadioButtonGroupProps { value?: T; diff --git a/packages/grafana-ui/src/components/Forms/commonStyles.ts b/packages/grafana-ui/src/components/Forms/commonStyles.ts index 04b00612770..4107d030e10 100644 --- a/packages/grafana-ui/src/components/Forms/commonStyles.ts +++ b/packages/grafana-ui/src/components/Forms/commonStyles.ts @@ -1,5 +1,7 @@ import { css, cx } from '@emotion/css'; + import { GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; + import { focusCss } from '../../themes/mixins'; import { ComponentSize } from '../../types/size'; diff --git a/packages/grafana-ui/src/components/Forms/getFormStyles.ts b/packages/grafana-ui/src/components/Forms/getFormStyles.ts index 6d0faf7b8e5..a730ab50efd 100644 --- a/packages/grafana-ui/src/components/Forms/getFormStyles.ts +++ b/packages/grafana-ui/src/components/Forms/getFormStyles.ts @@ -1,12 +1,14 @@ -import { stylesFactory } from '../../themes'; import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory } from '../../themes'; +import { ComponentSize } from '../../types/size'; +import { getButtonStyles, ButtonVariant } from '../Button'; +import { getInputStyles } from '../Input/Input'; + +import { getCheckboxStyles } from './Checkbox'; +import { getFieldValidationMessageStyles } from './FieldValidationMessage'; import { getLabelStyles } from './Label'; import { getLegendStyles } from './Legend'; -import { getFieldValidationMessageStyles } from './FieldValidationMessage'; -import { getButtonStyles, ButtonVariant } from '../Button'; -import { ComponentSize } from '../../types/size'; -import { getInputStyles } from '../Input/Input'; -import { getCheckboxStyles } from './Checkbox'; /** @deprecated */ export const getFormStyles = stylesFactory( diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx index 251e50e8d0a..9a1e0ebdd80 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + +import { ThresholdsMode, FieldConfig, FieldColorModeId } from '@grafana/data'; + +import { getTheme } from '../../themes'; import { Gauge, Props } from './Gauge'; -import { getTheme } from '../../themes'; -import { ThresholdsMode, FieldConfig, FieldColorModeId } from '@grafana/data'; jest.mock('jquery', () => ({ plot: jest.fn(), diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 0be0a4da88a..f35c53901f7 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -1,5 +1,6 @@ -import React, { PureComponent } from 'react'; import $ from 'jquery'; +import React, { PureComponent } from 'react'; + import { DisplayValue, formattedValueToString, @@ -14,6 +15,7 @@ import { FALLBACK_COLOR, TextDisplayOptions, } from '@grafana/data'; + import { Themeable } from '../../types'; import { calculateFontSize } from '../../utils/measureText'; diff --git a/packages/grafana-ui/src/components/Graph/Graph.test.tsx b/packages/grafana-ui/src/components/Graph/Graph.test.tsx index 68dcd1a3669..628538d33f7 100644 --- a/packages/grafana-ui/src/components/Graph/Graph.test.tsx +++ b/packages/grafana-ui/src/components/Graph/Graph.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { mount } from 'enzyme'; -import Graph from './Graph'; -import { TooltipDisplayMode } from '@grafana/schema'; -import { VizTooltip } from '../VizTooltip'; +import React from 'react'; + import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId, DisplayProcessor } from '@grafana/data'; +import { TooltipDisplayMode } from '@grafana/schema'; + +import { VizTooltip } from '../VizTooltip'; + +import Graph from './Graph'; const display: DisplayProcessor = (v) => ({ numeric: v, text: String(v), color: 'red' }); diff --git a/packages/grafana-ui/src/components/Graph/Graph.tsx b/packages/grafana-ui/src/components/Graph/Graph.tsx index 9938e158cf8..3a7d3aea487 100644 --- a/packages/grafana-ui/src/components/Graph/Graph.tsx +++ b/packages/grafana-ui/src/components/Graph/Graph.tsx @@ -1,15 +1,18 @@ // Libraries import $ from 'jquery'; -import React, { PureComponent } from 'react'; import { uniqBy } from 'lodash'; +import React, { PureComponent } from 'react'; + // Types -import { TooltipDisplayMode } from '@grafana/schema'; import { TimeRange, GraphSeriesXY, TimeZone, createDimension } from '@grafana/data'; -import { FlotPosition, FlotItem } from './types'; +import { TooltipDisplayMode } from '@grafana/schema'; + import { VizTooltipProps, VizTooltipContentProps, ActiveDimensions, VizTooltip } from '../VizTooltip'; -import { GraphTooltip } from './GraphTooltip/GraphTooltip'; + import { GraphContextMenu, GraphContextMenuProps, ContextDimensions } from './GraphContextMenu'; +import { GraphTooltip } from './GraphTooltip/GraphTooltip'; import { GraphDimensions } from './GraphTooltip/types'; +import { FlotPosition, FlotItem } from './types'; import { graphTimeFormat, graphTickFormatter } from './utils'; export interface GraphProps { diff --git a/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx b/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx index cd0dba40497..4ec9c631470 100644 --- a/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphContextMenu.tsx @@ -1,6 +1,6 @@ +import { css } from '@emotion/css'; import React from 'react'; -import { ContextMenu, ContextMenuProps } from '../ContextMenu/ContextMenu'; -import { GraphDimensions } from './GraphTooltip/types'; + import { FlotDataPoint, getValueFromDimension, @@ -9,13 +9,16 @@ import { TimeZone, FormattedValue, } from '@grafana/data'; + import { useTheme } from '../../themes'; -import { HorizontalGroup } from '../Layout/Layout'; +import { ContextMenu, ContextMenuProps } from '../ContextMenu/ContextMenu'; import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay'; -import { SeriesIcon } from '../VizLegend/SeriesIcon'; -import { css } from '@emotion/css'; +import { HorizontalGroup } from '../Layout/Layout'; import { MenuGroup, MenuGroupProps } from '../Menu/MenuGroup'; import { MenuItem } from '../Menu/MenuItem'; +import { SeriesIcon } from '../VizLegend/SeriesIcon'; + +import { GraphDimensions } from './GraphTooltip/types'; export type ContextDimensions = { [key in keyof T]: [number, number | undefined] | null }; diff --git a/packages/grafana-ui/src/components/Graph/GraphSeriesToggler.tsx b/packages/grafana-ui/src/components/Graph/GraphSeriesToggler.tsx index c646d090f45..cd1a33aa4d7 100644 --- a/packages/grafana-ui/src/components/Graph/GraphSeriesToggler.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphSeriesToggler.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { GraphSeriesXY } from '@grafana/data'; import { difference, isEqual } from 'lodash'; +import React from 'react'; + +import { GraphSeriesXY } from '@grafana/data'; export interface GraphSeriesTogglerAPI { onSeriesToggle: (label: string, event: React.MouseEvent) => void; diff --git a/packages/grafana-ui/src/components/Graph/GraphTooltip/GraphTooltip.tsx b/packages/grafana-ui/src/components/Graph/GraphTooltip/GraphTooltip.tsx index 6472dd40119..1e86a5da86e 100644 --- a/packages/grafana-ui/src/components/Graph/GraphTooltip/GraphTooltip.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphTooltip/GraphTooltip.tsx @@ -1,7 +1,9 @@ import React from 'react'; + import { VizTooltipContentProps } from '../../VizTooltip'; -import { SingleModeGraphTooltip } from './SingleModeGraphTooltip'; + import { MultiModeGraphTooltip } from './MultiModeGraphTooltip'; +import { SingleModeGraphTooltip } from './SingleModeGraphTooltip'; import { GraphDimensions } from './types'; export const GraphTooltip: React.FC> = ({ diff --git a/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.test.tsx b/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.test.tsx index 067627d0380..b3af9200fbb 100644 --- a/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.test.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { MultiModeGraphTooltip } from './MultiModeGraphTooltip'; +import React from 'react'; + import { createDimension, ArrayVector, FieldType, DisplayProcessor } from '@grafana/data'; -import { GraphDimensions } from './types'; + import { ActiveDimensions } from '../../VizTooltip'; +import { MultiModeGraphTooltip } from './MultiModeGraphTooltip'; +import { GraphDimensions } from './types'; + let dimensions: GraphDimensions; describe('MultiModeGraphTooltip', () => { diff --git a/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.tsx b/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.tsx index 73a1988704b..973d5e059d7 100644 --- a/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphTooltip/MultiModeGraphTooltip.tsx @@ -1,10 +1,13 @@ import React from 'react'; -import { SeriesTable } from '../../VizTooltip'; -import { GraphTooltipContentProps } from './types'; -import { getMultiSeriesGraphHoverInfo } from '../utils'; -import { FlotPosition } from '../types'; + import { getValueFromDimension } from '@grafana/data'; +import { SeriesTable } from '../../VizTooltip'; +import { FlotPosition } from '../types'; +import { getMultiSeriesGraphHoverInfo } from '../utils'; + +import { GraphTooltipContentProps } from './types'; + export const MultiModeGraphTooltip: React.FC< GraphTooltipContentProps & { // We expect position to figure out correct values when not hovering over a datapoint diff --git a/packages/grafana-ui/src/components/Graph/GraphTooltip/SingleModeGraphTooltip.tsx b/packages/grafana-ui/src/components/Graph/GraphTooltip/SingleModeGraphTooltip.tsx index 716fda9d34c..6335d6a7d76 100644 --- a/packages/grafana-ui/src/components/Graph/GraphTooltip/SingleModeGraphTooltip.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphTooltip/SingleModeGraphTooltip.tsx @@ -1,11 +1,14 @@ import React from 'react'; + import { getValueFromDimension, getColumnFromDimension, formattedValueToString, getFieldDisplayName, } from '@grafana/data'; + import { SeriesTable } from '../../VizTooltip'; + import { GraphTooltipContentProps } from './types'; export const SingleModeGraphTooltip: React.FC = ({ diff --git a/packages/grafana-ui/src/components/Graph/GraphTooltip/types.ts b/packages/grafana-ui/src/components/Graph/GraphTooltip/types.ts index bc807a9a93d..ccf04ef1175 100644 --- a/packages/grafana-ui/src/components/Graph/GraphTooltip/types.ts +++ b/packages/grafana-ui/src/components/Graph/GraphTooltip/types.ts @@ -1,6 +1,7 @@ -import { ActiveDimensions } from '../../VizTooltip'; import { Dimension, Dimensions, TimeZone } from '@grafana/data'; +import { ActiveDimensions } from '../../VizTooltip'; + export interface GraphDimensions extends Dimensions { xAxis: Dimension; yAxis: Dimension; diff --git a/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.internal.tsx b/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.internal.tsx index 03748655594..723e4d3c3b1 100644 --- a/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.internal.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.internal.tsx @@ -1,10 +1,12 @@ +import { Story } from '@storybook/react'; import React from 'react'; -import { Story } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { GraphWithLegend, GraphWithLegendProps } from './GraphWithLegend'; -import { LegendDisplayMode } from '@grafana/schema'; import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId } from '@grafana/data'; +import { LegendDisplayMode } from '@grafana/schema'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { GraphWithLegend, GraphWithLegendProps } from './GraphWithLegend'; export default { title: 'Visualizations/Graph/GraphWithLegend', diff --git a/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx b/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx index f65a981fbfa..693b8763024 100644 --- a/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx +++ b/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx @@ -1,15 +1,17 @@ // Libraries -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GraphSeriesValue } from '@grafana/data'; +import { LegendDisplayMode, LegendPlacement } from '@grafana/schema'; + +import { stylesFactory } from '../../themes'; +import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; +import { VizLegend } from '../VizLegend/VizLegend'; +import { VizLegendItem } from '../VizLegend/types'; import { Graph, GraphProps } from './Graph'; -import { VizLegendItem } from '../VizLegend/types'; -import { LegendDisplayMode, LegendPlacement } from '@grafana/schema'; -import { VizLegend } from '../VizLegend/VizLegend'; -import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; -import { stylesFactory } from '../../themes'; export interface GraphWithLegendProps extends GraphProps { legendDisplayMode: LegendDisplayMode; diff --git a/packages/grafana-ui/src/components/Graph/utils.test.ts b/packages/grafana-ui/src/components/Graph/utils.test.ts index f1209a088d5..742cd90acb6 100644 --- a/packages/grafana-ui/src/components/Graph/utils.test.ts +++ b/packages/grafana-ui/src/components/Graph/utils.test.ts @@ -9,7 +9,9 @@ import { createTheme, DataFrame, } from '@grafana/data'; + import { getTheme } from '../../themes'; + import { getMultiSeriesGraphHoverInfo, findHoverIndexFromData, graphTimeFormat } from './utils'; const mockResult = ( diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index e02a291226a..9783be08b37 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -1,7 +1,8 @@ import React from 'react'; +import { Subscription } from 'rxjs'; +import { throttleTime } from 'rxjs/operators'; import uPlot, { AlignedData } from 'uplot'; -import { Themeable2 } from '../../types'; -import { findMidPointYPosition, pluginLog } from '../uPlot/utils'; + import { DataFrame, DataHoverClearEvent, @@ -13,17 +14,19 @@ import { TimeRange, TimeZone, } from '@grafana/data'; -import { preparePlotFrame as defaultPreparePlotFrame } from './utils'; import { VizLegendOptions } from '@grafana/schema'; + +import { Themeable2 } from '../../types'; import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext'; -import { Subscription } from 'rxjs'; -import { throttleTime } from 'rxjs/operators'; -import { GraphNGLegendEvent, XYFieldMatchers } from './types'; -import { Renderers, UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; import { VizLayout } from '../VizLayout/VizLayout'; import { UPlotChart } from '../uPlot/Plot'; -import { ScaleProps } from '../uPlot/config/UPlotScaleBuilder'; import { AxisProps } from '../uPlot/config/UPlotAxisBuilder'; +import { Renderers, UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; +import { ScaleProps } from '../uPlot/config/UPlotScaleBuilder'; +import { findMidPointYPosition, pluginLog } from '../uPlot/utils'; + +import { GraphNGLegendEvent, XYFieldMatchers } from './types'; +import { preparePlotFrame as defaultPreparePlotFrame } from './utils'; /** * @internal -- not a public API diff --git a/packages/grafana-ui/src/components/GraphNG/hooks.ts b/packages/grafana-ui/src/components/GraphNG/hooks.ts index 1a8db1a0449..c21a7a9f775 100644 --- a/packages/grafana-ui/src/components/GraphNG/hooks.ts +++ b/packages/grafana-ui/src/components/GraphNG/hooks.ts @@ -1,7 +1,9 @@ -import { DataFrame, DataFrameFieldIndex, Field } from '@grafana/data'; -import { XYFieldMatchers } from './types'; import React, { useCallback, useContext } from 'react'; +import { DataFrame, DataFrameFieldIndex, Field } from '@grafana/data'; + +import { XYFieldMatchers } from './types'; + /** @alpha */ interface GraphNGContextType { mapSeriesIndexToDataFrameFieldIndex: (index: number) => DataFrameFieldIndex; diff --git a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts index f56b3f4d777..2888434cf9d 100644 --- a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts +++ b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts @@ -1,4 +1,5 @@ import { ArrayVector, FieldType, MutableDataFrame } from '@grafana/data'; + import { applyNullInsertThreshold } from './nullInsertThreshold'; function randInt(min: number, max: number) { diff --git a/packages/grafana-ui/src/components/GraphNG/types.ts b/packages/grafana-ui/src/components/GraphNG/types.ts index 33553ec5d5c..534cd9edf4d 100644 --- a/packages/grafana-ui/src/components/GraphNG/types.ts +++ b/packages/grafana-ui/src/components/GraphNG/types.ts @@ -1,4 +1,5 @@ import { DataFrameFieldIndex, FieldMatcher } from '@grafana/data'; + import { SeriesVisibilityChangeMode } from '../PanelChrome'; /** diff --git a/packages/grafana-ui/src/components/GraphNG/utils.test.ts b/packages/grafana-ui/src/components/GraphNG/utils.test.ts index 0cc6b59533c..31a4bb00209 100644 --- a/packages/grafana-ui/src/components/GraphNG/utils.test.ts +++ b/packages/grafana-ui/src/components/GraphNG/utils.test.ts @@ -1,8 +1,8 @@ -import { preparePlotFrame } from './utils'; -import { preparePlotConfigBuilder } from '../TimeSeries/utils'; import { + ArrayVector, createTheme, DashboardCursorSync, + DataFrame, DefaultTimeZone, EventBusSrv, FieldConfig, @@ -22,6 +22,10 @@ import { StackingMode, } from '@grafana/schema'; +import { preparePlotConfigBuilder } from '../TimeSeries/utils'; + +import { preparePlotFrame } from './utils'; + function mockDataFrame() { const df1 = new MutableDataFrame({ refId: 'A', @@ -202,4 +206,301 @@ describe('GraphNG utils', () => { }).getConfig(); expect(result).toMatchSnapshot(); }); + + test('preparePlotFrame appends min bar spaced nulls when > 1 bar series', () => { + const df1: DataFrame = { + name: 'A', + length: 5, + fields: [ + { + name: 'time', + type: FieldType.time, + config: {}, + values: new ArrayVector([1, 2, 4, 6, 100]), // should find smallest delta === 1 from here + }, + { + name: 'value', + type: FieldType.number, + config: { + custom: { + drawStyle: GraphDrawStyle.Bars, + }, + }, + values: new ArrayVector([1, 1, 1, 1, 1]), + }, + ], + }; + + const df2: DataFrame = { + name: 'B', + length: 5, + fields: [ + { + name: 'time', + type: FieldType.time, + config: {}, + values: new ArrayVector([30, 40, 50, 90, 100]), // should be appended with two smallest-delta increments + }, + { + name: 'value', + type: FieldType.number, + config: { + custom: { + drawStyle: GraphDrawStyle.Bars, + }, + }, + values: new ArrayVector([2, 2, 2, 2, 2]), // bar series should be appended with nulls + }, + { + name: 'value', + type: FieldType.number, + config: { + custom: { + drawStyle: GraphDrawStyle.Line, + }, + }, + values: new ArrayVector([3, 3, 3, 3, 3]), // line series should be appended with undefineds + }, + ], + }; + + const df3: DataFrame = { + name: 'C', + length: 2, + fields: [ + { + name: 'time', + type: FieldType.time, + config: {}, + values: new ArrayVector([1, 1.1]), // should not trip up on smaller deltas of non-bars + }, + { + name: 'value', + type: FieldType.number, + config: { + custom: { + drawStyle: GraphDrawStyle.Line, + }, + }, + values: new ArrayVector([4, 4]), + }, + { + name: 'value', + type: FieldType.number, + config: { + custom: { + drawStyle: GraphDrawStyle.Bars, + hideFrom: { + viz: true, // should ignore hidden bar series + }, + }, + }, + values: new ArrayVector([4, 4]), + }, + ], + }; + + let aligndFrame = preparePlotFrame([df1, df2, df3], { + x: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}), + y: fieldMatchers.get(FieldMatcherID.numeric).get({}), + }); + + expect(aligndFrame).toMatchInlineSnapshot(` + Object { + "fields": Array [ + Object { + "config": Object {}, + "name": "time", + "state": Object { + "origin": Object { + "fieldIndex": 0, + "frameIndex": 0, + }, + }, + "type": "time", + "values": Array [ + 1, + 1.1, + 2, + 4, + 6, + 30, + 40, + 50, + 90, + 100, + 101, + 102, + ], + }, + Object { + "config": Object { + "custom": Object { + "drawStyle": "bars", + "spanNulls": -1, + }, + }, + "labels": Object { + "name": "A", + }, + "name": "value", + "state": Object { + "origin": Object { + "fieldIndex": 1, + "frameIndex": 0, + }, + }, + "type": "number", + "values": Array [ + 1, + undefined, + 1, + 1, + 1, + undefined, + undefined, + undefined, + undefined, + 1, + null, + null, + ], + }, + Object { + "config": Object { + "custom": Object { + "drawStyle": "bars", + "spanNulls": -1, + }, + }, + "labels": Object { + "name": "B", + }, + "name": "value", + "state": Object { + "origin": Object { + "fieldIndex": 1, + "frameIndex": 1, + }, + }, + "type": "number", + "values": Array [ + undefined, + undefined, + undefined, + undefined, + undefined, + 2, + 2, + 2, + 2, + 2, + null, + null, + ], + }, + Object { + "config": Object { + "custom": Object { + "drawStyle": "line", + }, + }, + "labels": Object { + "name": "B", + }, + "name": "value", + "state": Object { + "origin": Object { + "fieldIndex": 2, + "frameIndex": 1, + }, + }, + "type": "number", + "values": Array [ + undefined, + undefined, + undefined, + undefined, + undefined, + 3, + 3, + 3, + 3, + 3, + undefined, + undefined, + ], + }, + Object { + "config": Object { + "custom": Object { + "drawStyle": "line", + }, + }, + "labels": Object { + "name": "C", + }, + "name": "value", + "state": Object { + "origin": Object { + "fieldIndex": 1, + "frameIndex": 2, + }, + }, + "type": "number", + "values": Array [ + 4, + 4, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ], + }, + Object { + "config": Object { + "custom": Object { + "drawStyle": "bars", + "hideFrom": Object { + "viz": true, + }, + }, + }, + "labels": Object { + "name": "C", + }, + "name": "value", + "state": Object { + "origin": Object { + "fieldIndex": 2, + "frameIndex": 2, + }, + }, + "type": "number", + "values": Array [ + 4, + 4, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ], + }, + ], + "length": 10, + } + `); + }); }); diff --git a/packages/grafana-ui/src/components/GraphNG/utils.ts b/packages/grafana-ui/src/components/GraphNG/utils.ts index 30450b0dd01..3abea36bb97 100644 --- a/packages/grafana-ui/src/components/GraphNG/utils.ts +++ b/packages/grafana-ui/src/components/GraphNG/utils.ts @@ -1,7 +1,4 @@ -import { XYFieldMatchers } from './types'; -import { ArrayVector, DataFrame, FieldConfig, FieldType, outerJoinDataFrames, TimeRange } from '@grafana/data'; -import { nullToUndefThreshold } from './nullToUndefThreshold'; -import { applyNullInsertThreshold } from './nullInsertThreshold'; +import { ArrayVector, DataFrame, Field, FieldConfig, FieldType, outerJoinDataFrames, TimeRange } from '@grafana/data'; import { AxisPlacement, GraphDrawStyle, @@ -9,7 +6,15 @@ import { ScaleDistribution, ScaleDistributionConfig, } from '@grafana/schema'; + import { FIXED_UNIT } from './GraphNG'; +import { applyNullInsertThreshold } from './nullInsertThreshold'; +import { nullToUndefThreshold } from './nullToUndefThreshold'; +import { XYFieldMatchers } from './types'; + +function isVisibleBarField(f: Field) { + return f.config.custom?.drawStyle === GraphDrawStyle.Bars && !f.config.custom?.hideFrom?.viz; +} // will mutate the DataFrame's fields' values function applySpanNullsThresholds(frame: DataFrame) { @@ -19,7 +24,7 @@ function applySpanNullsThresholds(frame: DataFrame) { for (let i = 0; i < frame.fields.length; i++) { let field = frame.fields[i]; - if (field === refField) { + if (field === refField || isVisibleBarField(field)) { continue; } @@ -36,30 +41,76 @@ function applySpanNullsThresholds(frame: DataFrame) { } export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers, timeRange?: TimeRange | null) { - let alignedFrame = outerJoinDataFrames({ - frames: frames.map((frame) => { - let fr = applyNullInsertThreshold(frame, null, timeRange?.to.valueOf()); + // apply null insertions at interval + frames = frames.map((frame) => applyNullInsertThreshold(frame, null, timeRange?.to.valueOf())); - // prevent minesweeper-expansion of nulls (gaps) when joining bars - // since bar width is determined from the minimum distance between non-undefined values - // (this strategy will still retain any original pre-join nulls, though) - fr.fields.forEach((f) => { - if (f.type === FieldType.number && f.config.custom?.drawStyle === GraphDrawStyle.Bars) { - f.config.custom = { - ...f.config.custom, - spanNulls: -1, - }; + let numBarSeries = 0; + + frames.forEach((frame) => { + frame.fields.forEach((f) => { + if (isVisibleBarField(f)) { + // prevent minesweeper-expansion of nulls (gaps) when joining bars + // since bar width is determined from the minimum distance between non-undefined values + // (this strategy will still retain any original pre-join nulls, though) + f.config.custom = { + ...f.config.custom, + spanNulls: -1, + }; + + numBarSeries++; + } + }); + }); + + // to make bar widths of all series uniform (equal to narrowest bar series), find smallest distance between x points + let minXDelta = Infinity; + + if (numBarSeries > 1) { + frames.forEach((frame) => { + if (!frame.fields.some(isVisibleBarField)) { + return; + } + + const xVals = frame.fields[0].values.toArray(); + + for (let i = 0; i < xVals.length; i++) { + if (i > 0) { + minXDelta = Math.min(minXDelta, xVals[i] - xVals[i - 1]); } - }); + } + }); + } - return fr; - }), + let alignedFrame = outerJoinDataFrames({ + frames, joinBy: dimFields.x, keep: dimFields.y, keepOriginIndices: true, }); - return alignedFrame && applySpanNullsThresholds(alignedFrame); + if (alignedFrame) { + alignedFrame = applySpanNullsThresholds(alignedFrame); + + // append 2 null vals at minXDelta to bar series + if (minXDelta !== Infinity) { + alignedFrame.fields.forEach((f, fi) => { + let vals = f.values.toArray(); + + if (fi === 0) { + let lastVal = vals[vals.length - 1]; + vals.push(lastVal + minXDelta, lastVal + 2 * minXDelta); + } else if (isVisibleBarField(f)) { + vals.push(null, null); + } else { + vals.push(undefined, undefined); + } + }); + } + + return alignedFrame; + } + + return null; } export function buildScaleKey(config: FieldConfig) { diff --git a/packages/grafana-ui/src/components/Icon/Icon.story.tsx b/packages/grafana-ui/src/components/Icon/Icon.story.tsx index 6a1a270ee26..5f817f080a2 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.story.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.story.tsx @@ -1,9 +1,12 @@ -import React, { ChangeEvent, useState } from 'react'; import { css } from '@emotion/css'; +import React, { ChangeEvent, useState } from 'react'; + import { Input, Field, Icon } from '@grafana/ui'; + +import { useTheme } from '../../themes'; import { getAvailableIcons, IconName } from '../../types'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { useTheme } from '../../themes'; + import mdx from './Icon.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index 4d78992869a..230a82a2c26 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; -import { stylesFactory } from '../../themes/stylesFactory'; -import { useTheme } from '../../themes/ThemeContext'; -import { IconName, IconType, IconSize } from '../../types/icon'; +import React from 'react'; import SVG from 'react-inlinesvg'; + +import { GrafanaTheme } from '@grafana/data'; + +import { useTheme } from '../../themes/ThemeContext'; +import { stylesFactory } from '../../themes/stylesFactory'; +import { IconName, IconType, IconSize } from '../../types/icon'; + import { cacheInitialized, initIconCache, iconRoot } from './iconBundle'; import { getIconSubDir, getSvgSize } from './utils'; diff --git a/packages/grafana-ui/src/components/Icon/iconBundle.ts b/packages/grafana-ui/src/components/Icon/iconBundle.ts index 3d7d280c83e..88e9fb7c5ac 100644 --- a/packages/grafana-ui/src/components/Icon/iconBundle.ts +++ b/packages/grafana-ui/src/components/Icon/iconBundle.ts @@ -1,15 +1,24 @@ // This file was autogenerated from // https://github.com/grafana/icons -import { cacheStore } from 'react-inlinesvg'; - -export let cacheInitialized = false; -export let iconRoot = 'public/img/icons'; - -function cacheItem(content: string, path: string) { - cacheStore[iconRoot + path] = { content, status: 'loaded', queue: [] }; -} - +import u1141 from '!!raw-loader!../../../../../public/img/icons/custom/gf-bar-alignment-after.svg'; +import u1142 from '!!raw-loader!../../../../../public/img/icons/custom/gf-bar-alignment-before.svg'; +import u1143 from '!!raw-loader!../../../../../public/img/icons/custom/gf-bar-alignment-center.svg'; +import u1137 from '!!raw-loader!../../../../../public/img/icons/custom/gf-grid.svg'; +import u1144 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-linear.svg'; +import u1145 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-smooth.svg'; +import u1146 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-step-after.svg'; +import u1147 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-step-before.svg'; +import u1138 from '!!raw-loader!../../../../../public/img/icons/custom/gf-landscape.svg'; +import u1139 from '!!raw-loader!../../../../../public/img/icons/custom/gf-layout-simple.svg'; +import u1148 from '!!raw-loader!../../../../../public/img/icons/custom/gf-logs.svg'; +import u1140 from '!!raw-loader!../../../../../public/img/icons/custom/gf-portrait.svg'; +import u1149 from '!!raw-loader!../../../../../public/img/icons/mono/favorite.svg'; +import u1150 from '!!raw-loader!../../../../../public/img/icons/mono/grafana.svg'; +import u1152 from '!!raw-loader!../../../../../public/img/icons/mono/heart-break.svg'; +import u1151 from '!!raw-loader!../../../../../public/img/icons/mono/heart.svg'; +import u1154 from '!!raw-loader!../../../../../public/img/icons/mono/library-panel.svg'; +import u1153 from '!!raw-loader!../../../../../public/img/icons/mono/panel-add.svg'; import u1001 from '!!raw-loader!../../../../../public/img/icons/unicons/angle-double-down.svg'; import u1002 from '!!raw-loader!../../../../../public/img/icons/unicons/angle-double-right.svg'; import u1003 from '!!raw-loader!../../../../../public/img/icons/unicons/angle-down.svg'; @@ -17,44 +26,47 @@ import u1004 from '!!raw-loader!../../../../../public/img/icons/unicons/angle-le import u1005 from '!!raw-loader!../../../../../public/img/icons/unicons/angle-right.svg'; import u1006 from '!!raw-loader!../../../../../public/img/icons/unicons/angle-up.svg'; import u1007 from '!!raw-loader!../../../../../public/img/icons/unicons/apps.svg'; -import u1008 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow.svg'; import u1009 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow-down.svg'; import u1010 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow-from-right.svg'; import u1011 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow-left.svg'; import u1012 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow-random.svg'; import u1013 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow-right.svg'; import u1014 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow-up.svg'; +import u1008 from '!!raw-loader!../../../../../public/img/icons/unicons/arrow.svg'; import u1015 from '!!raw-loader!../../../../../public/img/icons/unicons/arrows-h.svg'; import u1016 from '!!raw-loader!../../../../../public/img/icons/unicons/backward.svg'; import u1017 from '!!raw-loader!../../../../../public/img/icons/unicons/bars.svg'; -import u1018 from '!!raw-loader!../../../../../public/img/icons/unicons/bell.svg'; import u1019 from '!!raw-loader!../../../../../public/img/icons/unicons/bell-slash.svg'; +import u1018 from '!!raw-loader!../../../../../public/img/icons/unicons/bell.svg'; import u1020 from '!!raw-loader!../../../../../public/img/icons/unicons/bolt.svg'; -import u1021 from '!!raw-loader!../../../../../public/img/icons/unicons/book.svg'; import u1022 from '!!raw-loader!../../../../../public/img/icons/unicons/book-open.svg'; +import u1021 from '!!raw-loader!../../../../../public/img/icons/unicons/book.svg'; import u1023 from '!!raw-loader!../../../../../public/img/icons/unicons/brackets-curly.svg'; import u1024 from '!!raw-loader!../../../../../public/img/icons/unicons/bug.svg'; import u1025 from '!!raw-loader!../../../../../public/img/icons/unicons/building.svg'; import u1026 from '!!raw-loader!../../../../../public/img/icons/unicons/calculator-alt.svg'; import u1027 from '!!raw-loader!../../../../../public/img/icons/unicons/calendar-alt.svg'; import u1028 from '!!raw-loader!../../../../../public/img/icons/unicons/camera.svg'; +import u1136 from '!!raw-loader!../../../../../public/img/icons/unicons/capture.svg'; import u1029 from '!!raw-loader!../../../../../public/img/icons/unicons/channel-add.svg'; import u1030 from '!!raw-loader!../../../../../public/img/icons/unicons/chart-line.svg'; -import u1031 from '!!raw-loader!../../../../../public/img/icons/unicons/check.svg'; import u1032 from '!!raw-loader!../../../../../public/img/icons/unicons/check-circle.svg'; +import u1031 from '!!raw-loader!../../../../../public/img/icons/unicons/check.svg'; import u1033 from '!!raw-loader!../../../../../public/img/icons/unicons/circle.svg'; import u1034 from '!!raw-loader!../../../../../public/img/icons/unicons/clipboard-alt.svg'; import u1035 from '!!raw-loader!../../../../../public/img/icons/unicons/clock-nine.svg'; -import u1036 from '!!raw-loader!../../../../../public/img/icons/unicons/cloud.svg'; import u1037 from '!!raw-loader!../../../../../public/img/icons/unicons/cloud-download.svg'; +import u1122 from '!!raw-loader!../../../../../public/img/icons/unicons/cloud-upload.svg'; +import u1036 from '!!raw-loader!../../../../../public/img/icons/unicons/cloud.svg'; import u1038 from '!!raw-loader!../../../../../public/img/icons/unicons/code-branch.svg'; import u1039 from '!!raw-loader!../../../../../public/img/icons/unicons/cog.svg'; import u1040 from '!!raw-loader!../../../../../public/img/icons/unicons/columns.svg'; -import u1041 from '!!raw-loader!../../../../../public/img/icons/unicons/comment-alt.svg'; import u1042 from '!!raw-loader!../../../../../public/img/icons/unicons/comment-alt-share.svg'; +import u1041 from '!!raw-loader!../../../../../public/img/icons/unicons/comment-alt.svg'; import u1043 from '!!raw-loader!../../../../../public/img/icons/unicons/comments-alt.svg'; import u1044 from '!!raw-loader!../../../../../public/img/icons/unicons/compass.svg'; import u1045 from '!!raw-loader!../../../../../public/img/icons/unicons/copy.svg'; +import u1123 from '!!raw-loader!../../../../../public/img/icons/unicons/credit-card.svg'; import u1046 from '!!raw-loader!../../../../../public/img/icons/unicons/cube.svg'; import u1047 from '!!raw-loader!../../../../../public/img/icons/unicons/dashboard.svg'; import u1048 from '!!raw-loader!../../../../../public/img/icons/unicons/database.svg'; @@ -67,48 +79,60 @@ import u1054 from '!!raw-loader!../../../../../public/img/icons/unicons/envelope import u1055 from '!!raw-loader!../../../../../public/img/icons/unicons/exchange-alt.svg'; import u1056 from '!!raw-loader!../../../../../public/img/icons/unicons/exclamation-triangle.svg'; import u1057 from '!!raw-loader!../../../../../public/img/icons/unicons/external-link-alt.svg'; -import u1058 from '!!raw-loader!../../../../../public/img/icons/unicons/eye.svg'; import u1059 from '!!raw-loader!../../../../../public/img/icons/unicons/eye-slash.svg'; +import u1058 from '!!raw-loader!../../../../../public/img/icons/unicons/eye.svg'; import u1060 from '!!raw-loader!../../../../../public/img/icons/unicons/file-alt.svg'; import u1061 from '!!raw-loader!../../../../../public/img/icons/unicons/file-blank.svg'; +import u1124 from '!!raw-loader!../../../../../public/img/icons/unicons/file-copy-alt.svg'; import u1062 from '!!raw-loader!../../../../../public/img/icons/unicons/filter.svg'; -import u1063 from '!!raw-loader!../../../../../public/img/icons/unicons/folder.svg'; +import u1125 from '!!raw-loader!../../../../../public/img/icons/unicons/fire.svg'; import u1064 from '!!raw-loader!../../../../../public/img/icons/unicons/folder-open.svg'; import u1065 from '!!raw-loader!../../../../../public/img/icons/unicons/folder-plus.svg'; import u1066 from '!!raw-loader!../../../../../public/img/icons/unicons/folder-upload.svg'; +import u1063 from '!!raw-loader!../../../../../public/img/icons/unicons/folder.svg'; import u1067 from '!!raw-loader!../../../../../public/img/icons/unicons/forward.svg'; import u1068 from '!!raw-loader!../../../../../public/img/icons/unicons/graph-bar.svg'; import u1069 from '!!raw-loader!../../../../../public/img/icons/unicons/history.svg'; import u1070 from '!!raw-loader!../../../../../public/img/icons/unicons/home-alt.svg'; +import u1126 from '!!raw-loader!../../../../../public/img/icons/unicons/hourglass.svg'; import u1071 from '!!raw-loader!../../../../../public/img/icons/unicons/import.svg'; -import u1072 from '!!raw-loader!../../../../../public/img/icons/unicons/info.svg'; import u1073 from '!!raw-loader!../../../../../public/img/icons/unicons/info-circle.svg'; +import u1072 from '!!raw-loader!../../../../../public/img/icons/unicons/info.svg'; import u1074 from '!!raw-loader!../../../../../public/img/icons/unicons/key-skeleton-alt.svg'; import u1075 from '!!raw-loader!../../../../../public/img/icons/unicons/keyboard.svg'; +import u1127 from '!!raw-loader!../../../../../public/img/icons/unicons/layer-group.svg'; +import u1128 from '!!raw-loader!../../../../../public/img/icons/unicons/line-alt.svg'; import u1076 from '!!raw-loader!../../../../../public/img/icons/unicons/link.svg'; +import u1129 from '!!raw-loader!../../../../../public/img/icons/unicons/list-ui-alt.svg'; import u1077 from '!!raw-loader!../../../../../public/img/icons/unicons/list-ul.svg'; import u1078 from '!!raw-loader!../../../../../public/img/icons/unicons/lock.svg'; -import u1079 from '!!raw-loader!../../../../../public/img/icons/unicons/minus.svg'; +import u1130 from '!!raw-loader!../../../../../public/img/icons/unicons/message.svg'; import u1080 from '!!raw-loader!../../../../../public/img/icons/unicons/minus-circle.svg'; +import u1079 from '!!raw-loader!../../../../../public/img/icons/unicons/minus.svg'; import u1081 from '!!raw-loader!../../../../../public/img/icons/unicons/mobile-android.svg'; import u1082 from '!!raw-loader!../../../../../public/img/icons/unicons/monitor.svg'; +import u1131 from '!!raw-loader!../../../../../public/img/icons/unicons/palette.svg'; import u1083 from '!!raw-loader!../../../../../public/img/icons/unicons/pause.svg'; import u1084 from '!!raw-loader!../../../../../public/img/icons/unicons/pen.svg'; +import u1132 from '!!raw-loader!../../../../../public/img/icons/unicons/percentage.svg'; import u1085 from '!!raw-loader!../../../../../public/img/icons/unicons/play.svg'; import u1086 from '!!raw-loader!../../../../../public/img/icons/unicons/plug.svg'; -import u1087 from '!!raw-loader!../../../../../public/img/icons/unicons/plus.svg'; import u1088 from '!!raw-loader!../../../../../public/img/icons/unicons/plus-circle.svg'; +import u1134 from '!!raw-loader!../../../../../public/img/icons/unicons/plus-square.svg'; +import u1087 from '!!raw-loader!../../../../../public/img/icons/unicons/plus.svg'; import u1089 from '!!raw-loader!../../../../../public/img/icons/unicons/power.svg'; import u1090 from '!!raw-loader!../../../../../public/img/icons/unicons/presentation-play.svg'; import u1091 from '!!raw-loader!../../../../../public/img/icons/unicons/process.svg'; import u1092 from '!!raw-loader!../../../../../public/img/icons/unicons/question-circle.svg'; +import u1155 from '!!raw-loader!../../../../../public/img/icons/unicons/record-audio.svg'; import u1093 from '!!raw-loader!../../../../../public/img/icons/unicons/repeat.svg'; import u1094 from '!!raw-loader!../../../../../public/img/icons/unicons/rocket.svg'; import u1095 from '!!raw-loader!../../../../../public/img/icons/unicons/save.svg'; -import u1096 from '!!raw-loader!../../../../../public/img/icons/unicons/search.svg'; import u1097 from '!!raw-loader!../../../../../public/img/icons/unicons/search-minus.svg'; import u1098 from '!!raw-loader!../../../../../public/img/icons/unicons/search-plus.svg'; +import u1096 from '!!raw-loader!../../../../../public/img/icons/unicons/search.svg'; import u1099 from '!!raw-loader!../../../../../public/img/icons/unicons/share-alt.svg'; +import u1133 from '!!raw-loader!../../../../../public/img/icons/unicons/shield-exclamation.svg'; import u1100 from '!!raw-loader!../../../../../public/img/icons/unicons/shield.svg'; import u1101 from '!!raw-loader!../../../../../public/img/icons/unicons/signal.svg'; import u1102 from '!!raw-loader!../../../../../public/img/icons/unicons/signin.svg'; @@ -131,40 +155,15 @@ import u1118 from '!!raw-loader!../../../../../public/img/icons/unicons/upload.s import u1119 from '!!raw-loader!../../../../../public/img/icons/unicons/user.svg'; import u1120 from '!!raw-loader!../../../../../public/img/icons/unicons/users-alt.svg'; import u1121 from '!!raw-loader!../../../../../public/img/icons/unicons/wrap-text.svg'; -import u1122 from '!!raw-loader!../../../../../public/img/icons/unicons/cloud-upload.svg'; -import u1123 from '!!raw-loader!../../../../../public/img/icons/unicons/credit-card.svg'; -import u1124 from '!!raw-loader!../../../../../public/img/icons/unicons/file-copy-alt.svg'; -import u1125 from '!!raw-loader!../../../../../public/img/icons/unicons/fire.svg'; -import u1126 from '!!raw-loader!../../../../../public/img/icons/unicons/hourglass.svg'; -import u1127 from '!!raw-loader!../../../../../public/img/icons/unicons/layer-group.svg'; -import u1128 from '!!raw-loader!../../../../../public/img/icons/unicons/line-alt.svg'; -import u1129 from '!!raw-loader!../../../../../public/img/icons/unicons/list-ui-alt.svg'; -import u1130 from '!!raw-loader!../../../../../public/img/icons/unicons/message.svg'; -import u1131 from '!!raw-loader!../../../../../public/img/icons/unicons/palette.svg'; -import u1132 from '!!raw-loader!../../../../../public/img/icons/unicons/percentage.svg'; -import u1133 from '!!raw-loader!../../../../../public/img/icons/unicons/shield-exclamation.svg'; -import u1134 from '!!raw-loader!../../../../../public/img/icons/unicons/plus-square.svg'; import u1135 from '!!raw-loader!../../../../../public/img/icons/unicons/x.svg'; -import u1136 from '!!raw-loader!../../../../../public/img/icons/unicons/capture.svg'; -import u1137 from '!!raw-loader!../../../../../public/img/icons/custom/gf-grid.svg'; -import u1138 from '!!raw-loader!../../../../../public/img/icons/custom/gf-landscape.svg'; -import u1139 from '!!raw-loader!../../../../../public/img/icons/custom/gf-layout-simple.svg'; -import u1140 from '!!raw-loader!../../../../../public/img/icons/custom/gf-portrait.svg'; -import u1141 from '!!raw-loader!../../../../../public/img/icons/custom/gf-bar-alignment-after.svg'; -import u1142 from '!!raw-loader!../../../../../public/img/icons/custom/gf-bar-alignment-before.svg'; -import u1143 from '!!raw-loader!../../../../../public/img/icons/custom/gf-bar-alignment-center.svg'; -import u1144 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-linear.svg'; -import u1145 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-smooth.svg'; -import u1146 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-step-after.svg'; -import u1147 from '!!raw-loader!../../../../../public/img/icons/custom/gf-interpolation-step-before.svg'; -import u1148 from '!!raw-loader!../../../../../public/img/icons/custom/gf-logs.svg'; -import u1149 from '!!raw-loader!../../../../../public/img/icons/mono/favorite.svg'; -import u1150 from '!!raw-loader!../../../../../public/img/icons/mono/grafana.svg'; -import u1151 from '!!raw-loader!../../../../../public/img/icons/mono/heart.svg'; -import u1152 from '!!raw-loader!../../../../../public/img/icons/mono/heart-break.svg'; -import u1153 from '!!raw-loader!../../../../../public/img/icons/mono/panel-add.svg'; -import u1154 from '!!raw-loader!../../../../../public/img/icons/mono/library-panel.svg'; -import u1155 from '!!raw-loader!../../../../../public/img/icons/unicons/record-audio.svg'; +import { cacheStore } from 'react-inlinesvg'; + +export let cacheInitialized = false; +export let iconRoot = 'public/img/icons'; + +function cacheItem(content: string, path: string) { + cacheStore[iconRoot + path] = { content, status: 'loaded', queue: [] }; +} export function initIconCache() { cacheInitialized = true; diff --git a/packages/grafana-ui/src/components/IconButton/IconButton.story.tsx b/packages/grafana-ui/src/components/IconButton/IconButton.story.tsx index 3a0737d31c0..05e05318e94 100644 --- a/packages/grafana-ui/src/components/IconButton/IconButton.story.tsx +++ b/packages/grafana-ui/src/components/IconButton/IconButton.story.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { IconButton, IconButtonVariant } from './IconButton'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React from 'react'; + import { useTheme2 } from '../../themes'; import { IconSize, IconName } from '../../types'; -import mdx from './IconButton.mdx'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { VerticalGroup } from '../Layout/Layout'; +import { IconButton, IconButtonVariant } from './IconButton'; +import mdx from './IconButton.mdx'; + export default { title: 'Buttons/IconButton', component: IconButton, diff --git a/packages/grafana-ui/src/components/IconButton/IconButton.tsx b/packages/grafana-ui/src/components/IconButton/IconButton.tsx index 484fe64b002..dfddd2b1c9e 100644 --- a/packages/grafana-ui/src/components/IconButton/IconButton.tsx +++ b/packages/grafana-ui/src/components/IconButton/IconButton.tsx @@ -1,13 +1,15 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; + +import { GrafanaTheme2, colorManipulator } from '@grafana/data'; + +import { useTheme2 } from '../../themes/ThemeContext'; +import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; +import { stylesFactory } from '../../themes/stylesFactory'; +import { IconName, IconSize, IconType } from '../../types/icon'; import { Icon } from '../Icon/Icon'; import { getSvgSize } from '../Icon/utils'; -import { IconName, IconSize, IconType } from '../../types/icon'; -import { stylesFactory } from '../../themes/stylesFactory'; -import { css, cx } from '@emotion/css'; -import { useTheme2 } from '../../themes/ThemeContext'; -import { GrafanaTheme2, colorManipulator } from '@grafana/data'; import { TooltipPlacement, PopoverContent, Tooltip } from '../Tooltip'; -import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; export type IconButtonVariant = 'primary' | 'secondary' | 'destructive'; diff --git a/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx index 5e75f40b2c6..56ddec29a53 100644 --- a/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx @@ -1,9 +1,12 @@ +import { css } from '@emotion/css'; import React from 'react'; -import { InfoBox, InfoBoxProps } from './InfoBox'; + import { FeatureState, GrafanaTheme } from '@grafana/data'; + import { stylesFactory, useStyles } from '../../themes'; import { Badge, BadgeProps } from '../Badge/Badge'; -import { css } from '@emotion/css'; + +import { InfoBox, InfoBoxProps } from './InfoBox'; export interface FeatureInfoBoxProps extends Omit { title: string; diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx index 250d89a4167..b62014cd36e 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx @@ -1,10 +1,12 @@ +import { Story } from '@storybook/react'; import React from 'react'; + import { FeatureState } from '@grafana/data'; import { InfoBox, FeatureInfoBox, VerticalGroup } from '@grafana/ui'; -import mdx from './InfoBox.mdx'; -import { Story } from '@storybook/react'; + import { FeatureInfoBoxProps } from './FeatureInfoBox'; import { InfoBoxProps } from './InfoBox'; +import mdx from './InfoBox.mdx'; export default { title: 'Layout/InfoBox', diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx index 1a9421ebdaf..1d79acf7a74 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { Icon } from '../Icon/Icon'; -import { Alert, AlertVariant } from '../Alert/Alert'; + import { stylesFactory, useStyles2 } from '../../themes'; +import { Alert, AlertVariant } from '../Alert/Alert'; +import { Icon } from '../Icon/Icon'; export interface InfoBoxProps extends Omit, 'title'> { children: React.ReactNode; diff --git a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.story.internal.tsx b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.story.internal.tsx index 75a5abf8be2..10249e122bd 100644 --- a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.story.internal.tsx +++ b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.story.internal.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { InfoTooltip } from './InfoTooltip'; import { VizTooltip } from '../VizTooltip'; +import { InfoTooltip } from './InfoTooltip'; + export default { title: 'Overlays/TooltipInternal', component: VizTooltip, diff --git a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx index b0fd4d8202f..8eaaf3fa68f 100644 --- a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx +++ b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { TooltipProps, PopoverContent } from '../Tooltip'; + import { IconButton } from '../IconButton/IconButton'; +import { TooltipProps, PopoverContent } from '../Tooltip'; interface InfoTooltipProps extends Omit { children: PopoverContent; diff --git a/packages/grafana-ui/src/components/Input/Input.story.tsx b/packages/grafana-ui/src/components/Input/Input.story.tsx index 8ef80b2d9db..14e324e348f 100644 --- a/packages/grafana-ui/src/components/Input/Input.story.tsx +++ b/packages/grafana-ui/src/components/Input/Input.story.tsx @@ -1,11 +1,14 @@ -import React, { useState } from 'react'; import { Story, Meta } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import mdx from './Input.mdx'; -import { getAvailableIcons, IconName } from '../../types'; +import React, { useState } from 'react'; + import { KeyValue } from '@grafana/data'; import { Field, Icon, Button, Input } from '@grafana/ui'; +import { getAvailableIcons, IconName } from '../../types'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import mdx from './Input.mdx'; + const prefixSuffixOpts = { None: null, Text: '$', diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index e24d961514c..454e9763b3f 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -1,11 +1,13 @@ -import React, { HTMLProps, ReactNode } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { getFocusStyle, sharedInputStyle } from '../Forms/commonStyles'; -import { stylesFactory, useTheme2 } from '../../themes'; -import { Spinner } from '../Spinner/Spinner'; +import React, { HTMLProps, ReactNode } from 'react'; import useMeasure from 'react-use/lib/useMeasure'; +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory, useTheme2 } from '../../themes'; +import { getFocusStyle, sharedInputStyle } from '../Forms/commonStyles'; +import { Spinner } from '../Spinner/Spinner'; + export interface Props extends Omit, 'prefix' | 'size'> { /** Sets the width to a multiple of 8px. Should only be used with inline forms. Setting width of the container is preferred in other cases.*/ width?: number; diff --git a/packages/grafana-ui/src/components/JSONFormatter/JSONFormatter.tsx b/packages/grafana-ui/src/components/JSONFormatter/JSONFormatter.tsx index 0e405beb5a8..ca815e8c760 100644 --- a/packages/grafana-ui/src/components/JSONFormatter/JSONFormatter.tsx +++ b/packages/grafana-ui/src/components/JSONFormatter/JSONFormatter.tsx @@ -1,4 +1,5 @@ import React, { PureComponent, createRef } from 'react'; + import { JsonExplorer } from './json_explorer/json_explorer'; // We have made some monkey-patching of json-formatter-js so we can't switch right now interface Props { diff --git a/packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts b/packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts index 62e911b414f..c6eecd5da31 100644 --- a/packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts +++ b/packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts @@ -1,10 +1,10 @@ // Based on work https://github.com/mohsen1/json-formatter-js // License MIT, Copyright (c) 2015 Mohsen Azimi -import { isObject, getObjectName, getType, getValuePreview, cssClass, createElement } from './helpers'; - import { isNumber } from 'lodash'; +import { isObject, getObjectName, getType, getValuePreview, cssClass, createElement } from './helpers'; + const DATE_STRING_REGEX = /(^\d{1,4}[\.|\\/|-]\d{1,2}[\.|\\/|-]\d{1,4})(\s*(?:0?[1-9]:[0-5]|1(?=[012])\d:[0-5])\d\s*[ap]m)?$/; const PARTIAL_DATE_REGEX = /\d{2}:\d{2}:\d{2} GMT-\d{4}/; diff --git a/packages/grafana-ui/src/components/Layout/Layout.story.tsx b/packages/grafana-ui/src/components/Layout/Layout.story.tsx index c20f008707f..47f566b3352 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.story.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.story.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { Layout, LayoutProps } from './Layout'; -import { Button, VerticalGroup, HorizontalGroup } from '@grafana/ui'; -import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; import { Story, Meta } from '@storybook/react'; +import React from 'react'; + +import { Button, VerticalGroup, HorizontalGroup } from '@grafana/ui'; + +import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { withStoryContainer } from '../../utils/storybook/withStoryContainer'; + +import { Layout, LayoutProps } from './Layout'; import mdx from './Layout.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx index 2e53b8a3c0d..6b2ea79264e 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -1,6 +1,8 @@ -import React, { HTMLProps } from 'react'; import { css, cx } from '@emotion/css'; +import React, { HTMLProps } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { stylesFactory, useTheme } from '../../themes'; enum Orientation { diff --git a/packages/grafana-ui/src/components/Link/Link.tsx b/packages/grafana-ui/src/components/Link/Link.tsx index 4510d463428..0b9e9c80dd1 100644 --- a/packages/grafana-ui/src/components/Link/Link.tsx +++ b/packages/grafana-ui/src/components/Link/Link.tsx @@ -1,7 +1,8 @@ -import { locationUtil, textUtil } from '@grafana/data'; import React, { AnchorHTMLAttributes, forwardRef } from 'react'; import { Link as RouterLink } from 'react-router-dom'; +import { locationUtil, textUtil } from '@grafana/data'; + export interface Props extends AnchorHTMLAttributes {} /** diff --git a/packages/grafana-ui/src/components/List/AbstractList.test.tsx b/packages/grafana-ui/src/components/List/AbstractList.test.tsx index 6538a806d61..c1265cf59c9 100644 --- a/packages/grafana-ui/src/components/List/AbstractList.test.tsx +++ b/packages/grafana-ui/src/components/List/AbstractList.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { AbstractList } from './AbstractList'; describe('AbstractList', () => { diff --git a/packages/grafana-ui/src/components/List/AbstractList.tsx b/packages/grafana-ui/src/components/List/AbstractList.tsx index f372dfdf30d..e0d3e54f8a2 100644 --- a/packages/grafana-ui/src/components/List/AbstractList.tsx +++ b/packages/grafana-ui/src/components/List/AbstractList.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { cx, css } from '@emotion/css'; +import React from 'react'; + import { stylesFactory } from '../../themes'; export interface ListProps { diff --git a/packages/grafana-ui/src/components/List/InlineList.tsx b/packages/grafana-ui/src/components/List/InlineList.tsx index aadfd84046f..bcb9f2a77f0 100644 --- a/packages/grafana-ui/src/components/List/InlineList.tsx +++ b/packages/grafana-ui/src/components/List/InlineList.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { ListProps, AbstractList } from './AbstractList'; export class InlineList extends React.PureComponent> { diff --git a/packages/grafana-ui/src/components/List/List.story.internal.tsx b/packages/grafana-ui/src/components/List/List.story.internal.tsx index f9111158270..2a7a74dfddc 100644 --- a/packages/grafana-ui/src/components/List/List.story.internal.tsx +++ b/packages/grafana-ui/src/components/List/List.story.internal.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import { Story, Meta } from '@storybook/react'; -import { List } from './List'; import { css, cx } from '@emotion/css'; +import { Story, Meta } from '@storybook/react'; +import React from 'react'; import tinycolor from 'tinycolor2'; + import { InlineList } from './InlineList'; +import { List } from './List'; export default { title: 'Layout/List', diff --git a/packages/grafana-ui/src/components/List/List.tsx b/packages/grafana-ui/src/components/List/List.tsx index 1fcc7d9ae01..70730f9d0e4 100644 --- a/packages/grafana-ui/src/components/List/List.tsx +++ b/packages/grafana-ui/src/components/List/List.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { ListProps, AbstractList } from './AbstractList'; export class List extends React.PureComponent> { diff --git a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.story.tsx b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.story.tsx index ad8215e0aaa..450dad89719 100644 --- a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.story.tsx +++ b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.story.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { LoadingPlaceholder } from './LoadingPlaceholder'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { LoadingPlaceholder } from './LoadingPlaceholder'; import mdx from './LoadingPlaceholder.mdx'; export default { diff --git a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx index 233a14020a3..968c1c3039a 100644 --- a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx +++ b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx @@ -1,8 +1,10 @@ -import React, { HTMLAttributes, SFC } from 'react'; import { css, cx } from '@emotion/css'; +import React, { HTMLAttributes, SFC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { Spinner } from '../Spinner/Spinner'; + import { useStyles } from '../../themes'; +import { Spinner } from '../Spinner/Spinner'; /** * @public diff --git a/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx b/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx index e0b68c0bbaa..dfa3dc70505 100644 --- a/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogDetails.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen, within } from '@testing-library/react'; +import React from 'react'; + import { Field, GrafanaTheme2, LogLevel, LogRowModel, MutableDataFrame } from '@grafana/data'; import { LogDetails, Props } from './LogDetails'; diff --git a/packages/grafana-ui/src/components/Logs/LogDetails.tsx b/packages/grafana-ui/src/components/Logs/LogDetails.tsx index b5d2adbc943..4c9d0262c21 100644 --- a/packages/grafana-ui/src/components/Logs/LogDetails.tsx +++ b/packages/grafana-ui/src/components/Logs/LogDetails.tsx @@ -1,6 +1,7 @@ -import React, { PureComponent } from 'react'; -import memoizeOne from 'memoize-one'; import { css, cx } from '@emotion/css'; +import memoizeOne from 'memoize-one'; +import React, { PureComponent } from 'react'; + import { calculateFieldStats, calculateLogsLabelStats, @@ -12,15 +13,16 @@ import { GrafanaTheme2, } from '@grafana/data'; -import { Themeable2 } from '../../types/theme'; import { withTheme2 } from '../../themes/index'; +import { Themeable2 } from '../../types/theme'; +import { Icon } from '../Icon/Icon'; +import { Tooltip } from '../Tooltip/Tooltip'; + +import { LogDetailsRow } from './LogDetailsRow'; import { getLogRowStyles } from './getLogRowStyles'; import { getAllFields } from './logParser'; //Components -import { LogDetailsRow } from './LogDetailsRow'; -import { Tooltip } from '../Tooltip/Tooltip'; -import { Icon } from '../Icon/Icon'; export interface Props extends Themeable2 { row: LogRowModel; diff --git a/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx b/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx index 781320c1f70..11d470c0edd 100644 --- a/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogDetailsRow.test.tsx @@ -1,5 +1,6 @@ -import React, { ComponentProps } from 'react'; import { screen, render, fireEvent } from '@testing-library/react'; +import React, { ComponentProps } from 'react'; + import { LogDetailsRow } from './LogDetailsRow'; type Props = ComponentProps; diff --git a/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx b/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx index e20cdf055fe..d483403f414 100644 --- a/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx @@ -1,15 +1,17 @@ -import React, { PureComponent } from 'react'; import { css, cx } from '@emotion/css'; +import React, { PureComponent } from 'react'; + import { Field, LinkModel, LogLabelStatsModel, GrafanaTheme2 } from '@grafana/data'; -import { Themeable2 } from '../../types/theme'; import { withTheme2 } from '../../themes/index'; +import { Themeable2 } from '../../types/theme'; +import { DataLinkButton } from '../DataLinks/DataLinkButton'; +import { IconButton } from '../IconButton/IconButton'; + +import { LogLabelStats } from './LogLabelStats'; import { getLogRowStyles } from './getLogRowStyles'; //Components -import { LogLabelStats } from './LogLabelStats'; -import { IconButton } from '../IconButton/IconButton'; -import { DataLinkButton } from '../DataLinks/DataLinkButton'; export interface Props extends Themeable2 { parsedValue: string; diff --git a/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx b/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx index fd5aa50d4d5..609a8065bfd 100644 --- a/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx +++ b/packages/grafana-ui/src/components/Logs/LogLabelStats.tsx @@ -1,10 +1,11 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + import { LogLabelStatsModel, GrafanaTheme } from '@grafana/data'; -import { Themeable } from '../../types/theme'; import { stylesFactory } from '../../themes'; import { withTheme } from '../../themes/index'; +import { Themeable } from '../../types/theme'; //Components import { LogLabelStatsRow } from './LogLabelStatsRow'; diff --git a/packages/grafana-ui/src/components/Logs/LogLabelStatsRow.tsx b/packages/grafana-ui/src/components/Logs/LogLabelStatsRow.tsx index d477a1851e7..e18637678de 100644 --- a/packages/grafana-ui/src/components/Logs/LogLabelStatsRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogLabelStatsRow.tsx @@ -1,6 +1,8 @@ -import React, { FunctionComponent } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FunctionComponent } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { useStyles } from '../../themes/ThemeContext'; const getStyles = (theme: GrafanaTheme) => ({ diff --git a/packages/grafana-ui/src/components/Logs/LogLabels.test.tsx b/packages/grafana-ui/src/components/Logs/LogLabels.test.tsx index eaee962f963..fc1a4f23120 100644 --- a/packages/grafana-ui/src/components/Logs/LogLabels.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogLabels.test.tsx @@ -1,8 +1,9 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + +import { getTheme } from '../../themes'; import { UnThemedLogLabels as LogLabels } from './LogLabels'; -import { getTheme } from '../../themes'; describe('', () => { it('renders notice when no labels are found', () => { diff --git a/packages/grafana-ui/src/components/Logs/LogLabels.tsx b/packages/grafana-ui/src/components/Logs/LogLabels.tsx index 0c88e4c2239..aad16e60cbf 100644 --- a/packages/grafana-ui/src/components/Logs/LogLabels.tsx +++ b/packages/grafana-ui/src/components/Logs/LogLabels.tsx @@ -1,10 +1,11 @@ -import React, { FunctionComponent } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FunctionComponent } from 'react'; + import { GrafanaTheme, Labels } from '@grafana/data'; import { stylesFactory } from '../../themes'; -import { Themeable } from '../../types/theme'; import { withTheme } from '../../themes/ThemeContext'; +import { Themeable } from '../../types/theme'; // Levels are already encoded in color, filename is a Loki-ism const HIDDEN_LABELS = ['level', 'lvl', 'filename']; diff --git a/packages/grafana-ui/src/components/Logs/LogMessageAnsi.test.tsx b/packages/grafana-ui/src/components/Logs/LogMessageAnsi.test.tsx index 4dac18ce2e8..8bafbb9725a 100644 --- a/packages/grafana-ui/src/components/Logs/LogMessageAnsi.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogMessageAnsi.test.tsx @@ -1,6 +1,8 @@ -import { createTheme } from '@grafana/data'; import { shallow } from 'enzyme'; import React from 'react'; + +import { createTheme } from '@grafana/data'; + import { UnThemedLogMessageAnsi as LogMessageAnsi } from './LogMessageAnsi'; describe('', () => { diff --git a/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx b/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx index 478b4f68236..b7633b714b5 100644 --- a/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx +++ b/packages/grafana-ui/src/components/Logs/LogMessageAnsi.tsx @@ -1,8 +1,10 @@ -import { findHighlightChunksInText, GrafanaTheme2 } from '@grafana/data'; import ansicolor from 'ansicolor'; import React, { PureComponent } from 'react'; // @ts-ignore import Highlighter from 'react-highlight-words'; + +import { findHighlightChunksInText, GrafanaTheme2 } from '@grafana/data'; + import { withTheme2 } from '../../themes'; import { Themeable2 } from '../../types'; diff --git a/packages/grafana-ui/src/components/Logs/LogRow.tsx b/packages/grafana-ui/src/components/Logs/LogRow.tsx index 5c2c2b6a9ed..dc62deea068 100644 --- a/packages/grafana-ui/src/components/Logs/LogRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRow.tsx @@ -1,4 +1,6 @@ +import { cx, css } from '@emotion/css'; import React, { PureComponent } from 'react'; + import { Field, LinkModel, @@ -11,10 +13,14 @@ import { escapeUnescapedString, GrafanaTheme2, } from '@grafana/data'; + +import { styleMixins, withTheme2 } from '../../themes/index'; +import { Themeable2 } from '../../types/theme'; import { Icon } from '../Icon/Icon'; import { Tooltip } from '../Tooltip/Tooltip'; -import { cx, css } from '@emotion/css'; +import { LogDetails } from './LogDetails'; +import { LogLabels } from './LogLabels'; import { LogRowContextRows, LogRowContextQueryErrors, @@ -22,15 +28,11 @@ import { LogRowContextProvider, RowContextOptions, } from './LogRowContextProvider'; -import { Themeable2 } from '../../types/theme'; -import { styleMixins, withTheme2 } from '../../themes/index'; +import { LogRowMessage } from './LogRowMessage'; +import { LogRowMessageDetectedFields } from './LogRowMessageDetectedFields'; import { getLogRowStyles } from './getLogRowStyles'; //Components -import { LogDetails } from './LogDetails'; -import { LogRowMessageDetectedFields } from './LogRowMessageDetectedFields'; -import { LogRowMessage } from './LogRowMessage'; -import { LogLabels } from './LogLabels'; interface Props extends Themeable2 { row: LogRowModel; diff --git a/packages/grafana-ui/src/components/Logs/LogRowContext.test.tsx b/packages/grafana-ui/src/components/Logs/LogRowContext.test.tsx index 759f34880c9..b63d7f8f791 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowContext.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowContext.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; -import { LogRowModel } from '@grafana/data'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { LogRowModel } from '@grafana/data'; + import { LogRowContextGroup } from './LogRowContext'; describe('LogRowContextGroup component', () => { diff --git a/packages/grafana-ui/src/components/Logs/LogRowContext.tsx b/packages/grafana-ui/src/components/Logs/LogRowContext.tsx index f5f2a38667d..ee68d49fd3d 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowContext.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowContext.tsx @@ -1,14 +1,16 @@ -import React, { useRef, useState, useLayoutEffect, useEffect } from 'react'; -import { GrafanaTheme, DataQueryError, LogRowModel, textUtil } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { useRef, useState, useLayoutEffect, useEffect } from 'react'; + +import { GrafanaTheme, DataQueryError, LogRowModel, textUtil } from '@grafana/data'; -import { Alert } from '../Alert/Alert'; -import { LogRowContextRows, LogRowContextQueryErrors, HasMoreContextRows } from './LogRowContextProvider'; import { useStyles, useTheme } from '../../themes/ThemeContext'; +import { Alert } from '../Alert/Alert'; +import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; import { List } from '../List/List'; -import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper'; + import { LogMessageAnsi } from './LogMessageAnsi'; +import { LogRowContextRows, LogRowContextQueryErrors, HasMoreContextRows } from './LogRowContextProvider'; interface LogRowContextProps { row: LogRowModel; diff --git a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx index 62f03812b2a..4ea6cfd27d6 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { FieldType, LogRowModel, MutableDataFrame, Labels, LogLevel, DataQueryResponse } from '@grafana/data'; -import { getRowContexts, LogRowContextProvider } from './LogRowContextProvider'; import { mount } from 'enzyme'; +import React from 'react'; import { act } from 'react-dom/test-utils'; +import { FieldType, LogRowModel, MutableDataFrame, Labels, LogLevel, DataQueryResponse } from '@grafana/data'; + +import { getRowContexts, LogRowContextProvider } from './LogRowContextProvider'; + describe('getRowContexts', () => { describe('when called with a DataFrame and results are returned', () => { it('then the result should be in correct format and filtered', async () => { diff --git a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.tsx b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.tsx index c1858755633..2751f623c12 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.tsx @@ -1,3 +1,6 @@ +import React, { useState, useEffect } from 'react'; +import useAsync from 'react-use/lib/useAsync'; + import { LogRowModel, toDataFrame, @@ -7,8 +10,6 @@ import { DataQueryResponse, DataQueryError, } from '@grafana/data'; -import React, { useState, useEffect } from 'react'; -import useAsync from 'react-use/lib/useAsync'; export interface RowContextOptions { direction?: 'BACKWARD' | 'FORWARD'; diff --git a/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx b/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx index 140bb0a733f..7a8e8694245 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx @@ -1,19 +1,23 @@ -import React, { PureComponent } from 'react'; -import tinycolor from 'tinycolor2'; import { css, cx } from '@emotion/css'; -import { LogRowModel, findHighlightChunksInText, GrafanaTheme2 } from '@grafana/data'; import memoizeOne from 'memoize-one'; - +import React, { PureComponent } from 'react'; // @ts-ignore import Highlighter from 'react-highlight-words'; -import { LogRowContextQueryErrors, HasMoreContextRows, LogRowContextRows } from './LogRowContextProvider'; -import { Themeable2 } from '../../types/theme'; +import tinycolor from 'tinycolor2'; + +import { LogRowModel, findHighlightChunksInText, GrafanaTheme2 } from '@grafana/data'; + +// @ts-ignore + import { withTheme2 } from '../../themes/index'; +import { Themeable2 } from '../../types/theme'; + +import { LogMessageAnsi } from './LogMessageAnsi'; +import { LogRowContext } from './LogRowContext'; +import { LogRowContextQueryErrors, HasMoreContextRows, LogRowContextRows } from './LogRowContextProvider'; import { getLogRowStyles } from './getLogRowStyles'; //Components -import { LogRowContext } from './LogRowContext'; -import { LogMessageAnsi } from './LogMessageAnsi'; export const MAX_CHARACTERS = 100000; diff --git a/packages/grafana-ui/src/components/Logs/LogRowMessageDetectedFields.tsx b/packages/grafana-ui/src/components/Logs/LogRowMessageDetectedFields.tsx index f1a174b52fa..15415f0a406 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowMessageDetectedFields.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowMessageDetectedFields.tsx @@ -1,9 +1,10 @@ -import React, { PureComponent } from 'react'; import { cx, css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + import { LogRowModel, Field, LinkModel } from '@grafana/data'; -import { Themeable } from '../../types/theme'; import { withTheme } from '../../themes/index'; +import { Themeable } from '../../types/theme'; import { getAllFields } from './logParser'; diff --git a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx index 4de84bae8da..7b9cd409578 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { range } from 'lodash'; -import { LogRows, PREVIEW_LIMIT } from './LogRows'; import { mount } from 'enzyme'; +import { range } from 'lodash'; +import React from 'react'; + import { LogLevel, LogRowModel, LogsDedupStrategy, MutableDataFrame, LogsSortOrder } from '@grafana/data'; + import { LogRow } from './LogRow'; +import { LogRows, PREVIEW_LIMIT } from './LogRows'; describe('LogRows', () => { it('renders rows', () => { diff --git a/packages/grafana-ui/src/components/Logs/LogRows.tsx b/packages/grafana-ui/src/components/Logs/LogRows.tsx index 0971ddba9ee..a59d3c71b15 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.tsx @@ -1,14 +1,15 @@ -import React, { PureComponent } from 'react'; import memoizeOne from 'memoize-one'; +import React, { PureComponent } from 'react'; + import { TimeZone, LogsDedupStrategy, LogRowModel, Field, LinkModel, LogsSortOrder, sortLogRows } from '@grafana/data'; -import { Themeable2 } from '../../types/theme'; import { withTheme2 } from '../../themes/index'; -import { getLogRowStyles } from './getLogRowStyles'; +import { Themeable2 } from '../../types/theme'; //Components import { LogRow } from './LogRow'; import { RowContextOptions } from './LogRowContextProvider'; +import { getLogRowStyles } from './getLogRowStyles'; export const PREVIEW_LIMIT = 100; diff --git a/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts b/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts index 7bf0896aceb..d97196c8701 100644 --- a/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts +++ b/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts @@ -1,5 +1,7 @@ import { css } from '@emotion/css'; + import { GrafanaTheme2, LogLevel } from '@grafana/data'; + import { styleMixins, stylesFactory } from '../../themes'; export const getLogRowStyles = stylesFactory((theme: GrafanaTheme2, logLevel?: LogLevel) => { diff --git a/packages/grafana-ui/src/components/Logs/logParser.ts b/packages/grafana-ui/src/components/Logs/logParser.ts index a8ff5725bb6..ecb11fcade4 100644 --- a/packages/grafana-ui/src/components/Logs/logParser.ts +++ b/packages/grafana-ui/src/components/Logs/logParser.ts @@ -1,6 +1,7 @@ -import { Field, getParser, LinkModel, LogRowModel } from '@grafana/data'; import memoizeOne from 'memoize-one'; +import { Field, getParser, LinkModel, LogRowModel } from '@grafana/data'; + import { MAX_CHARACTERS } from './LogRowMessage'; const memoizedGetParser = memoizeOne(getParser); diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx index 48739edad90..5a99d3bd304 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNameByRegexMatcherEditor.tsx @@ -1,8 +1,11 @@ import React, { memo, useCallback } from 'react'; -import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + import { FieldMatcherID, fieldMatchers } from '@grafana/data'; + import { Input } from '../Input/Input'; +import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + export const FieldNameByRegexMatcherEditor = memo>((props) => { const { options, onChange } = props; diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx index d2d4b9249cf..89dcb35fcb4 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNameMatcherEditor.tsx @@ -1,7 +1,10 @@ import React, { memo, useCallback } from 'react'; -import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + import { FieldMatcherID, fieldMatchers, SelectableValue } from '@grafana/data'; + import { Select } from '../Select/Select'; + +import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils'; export const FieldNameMatcherEditor = memo>((props) => { diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNamePicker.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNamePicker.tsx index da26fd7c0a4..e483e2fd3c8 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNamePicker.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNamePicker.tsx @@ -1,6 +1,9 @@ import React, { useCallback } from 'react'; + import { FieldNamePickerConfigSettings, SelectableValue, StandardEditorProps } from '@grafana/data'; + import { Select } from '../Select/Select'; + import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils'; // Pick a field name out of the fulds diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx index 3d52275d9eb..3e37f6f9734 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldNamesMatcherEditor.tsx @@ -1,8 +1,11 @@ import React, { memo, useCallback } from 'react'; -import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + import { FieldMatcherID, fieldMatchers, SelectableValue, ByNamesMatcherOptions } from '@grafana/data'; -import { MultiSelect } from '../Select/Select'; + import { Input } from '../Input/Input'; +import { MultiSelect } from '../Select/Select'; + +import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils'; export const FieldNamesMatcherEditor = memo>((props) => { diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx index 6c9a0c78304..a4f3a1de274 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldTypeMatcherEditor.tsx @@ -1,8 +1,11 @@ import React, { memo, useMemo, useCallback } from 'react'; -import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + import { FieldMatcherID, fieldMatchers, SelectableValue, FieldType, DataFrame } from '@grafana/data'; + import { Select } from '../Select/Select'; +import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + export const FieldTypeMatcherEditor = memo>((props) => { const { data, options, onChange: onChangeFromProps, id } = props; const counts = useFieldCounts(data); diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx index 57c78198b80..b50cda2db54 100644 --- a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx +++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.tsx @@ -1,8 +1,11 @@ import React, { memo, useMemo, useCallback } from 'react'; -import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + import { FieldMatcherID, fieldMatchers, SelectableValue, DataFrame } from '@grafana/data'; + import { Select } from '../Select/Select'; +import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types'; + /** * UI to configure "fields by frame refId"-matcher. * @public diff --git a/packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts b/packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts index 26cda2b0ad4..72c22e193c5 100644 --- a/packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts +++ b/packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts @@ -1,10 +1,11 @@ import { Registry } from '@grafana/data'; -import { FieldMatcherUIRegistryItem } from './types'; -import { fieldNameMatcherItem } from './FieldNameMatcherEditor'; + import { fieldNameByRegexMatcherItem } from './FieldNameByRegexMatcherEditor'; +import { fieldNameMatcherItem } from './FieldNameMatcherEditor'; +import { fieldNamesMatcherItem } from './FieldNamesMatcherEditor'; import { fieldTypeMatcherItem } from './FieldTypeMatcherEditor'; import { fieldsByFrameRefIdItem } from './FieldsByFrameRefIdMatcher'; -import { fieldNamesMatcherItem } from './FieldNamesMatcherEditor'; +import { FieldMatcherUIRegistryItem } from './types'; export const fieldMatchersUI = new Registry>(() => [ fieldNameMatcherItem, diff --git a/packages/grafana-ui/src/components/MatchersUI/types.ts b/packages/grafana-ui/src/components/MatchersUI/types.ts index 64e4a2d9515..868adc8a527 100644 --- a/packages/grafana-ui/src/components/MatchersUI/types.ts +++ b/packages/grafana-ui/src/components/MatchersUI/types.ts @@ -1,4 +1,5 @@ import React from 'react'; + import { DataFrame, RegistryItem, FieldMatcherInfo } from '@grafana/data'; export interface FieldMatcherUIRegistryItem extends RegistryItem { diff --git a/packages/grafana-ui/src/components/MatchersUI/utils.ts b/packages/grafana-ui/src/components/MatchersUI/utils.ts index ccbf9545081..490a514009a 100644 --- a/packages/grafana-ui/src/components/MatchersUI/utils.ts +++ b/packages/grafana-ui/src/components/MatchersUI/utils.ts @@ -1,5 +1,7 @@ import { useMemo } from 'react'; + import { DataFrame, Field, getFieldDisplayName, SelectableValue } from '@grafana/data'; + import { getFieldTypeIcon } from '../../types'; /** diff --git a/packages/grafana-ui/src/components/Menu/Menu.story.internal.tsx b/packages/grafana-ui/src/components/Menu/Menu.story.internal.tsx index 896162f0e2f..e3513121c61 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.story.internal.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.story.internal.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { Story } from '@storybook/react'; -import { Menu, MenuProps } from './Menu'; -import { MenuItem } from './MenuItem'; -import { MenuGroup } from './MenuGroup'; +import React from 'react'; + import { GraphContextMenuHeader } from '..'; import { StoryExample } from '../../utils/storybook/StoryExample'; import { VerticalGroup } from '../Layout/Layout'; +import { Menu, MenuProps } from './Menu'; +import { MenuGroup } from './MenuGroup'; +import { MenuItem } from './MenuItem'; + export default { title: 'General/Menu', component: Menu, diff --git a/packages/grafana-ui/src/components/Menu/Menu.test.tsx b/packages/grafana-ui/src/components/Menu/Menu.test.tsx index cd29211bca7..0df2af1c7ad 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.test.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; + import { Menu } from './Menu'; import { MenuGroup } from './MenuGroup'; import { MenuItem } from './MenuItem'; diff --git a/packages/grafana-ui/src/components/Menu/Menu.tsx b/packages/grafana-ui/src/components/Menu/Menu.tsx index 60773029cb8..1104dac5e9d 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.tsx @@ -1,7 +1,10 @@ -import React, { useImperativeHandle, useRef } from 'react'; import { css } from '@emotion/css'; +import React, { useImperativeHandle, useRef } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; + import { useMenuFocus } from './hooks'; /** @internal */ diff --git a/packages/grafana-ui/src/components/Menu/MenuGroup.tsx b/packages/grafana-ui/src/components/Menu/MenuGroup.tsx index b730ec8685c..2054dde3b6c 100644 --- a/packages/grafana-ui/src/components/Menu/MenuGroup.tsx +++ b/packages/grafana-ui/src/components/Menu/MenuGroup.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '../../themes'; -import { MenuItemProps } from './MenuItem'; import { uniqueId } from 'lodash'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; + +import { MenuItemProps } from './MenuItem'; /** @internal */ export interface MenuItemsGroup { diff --git a/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx b/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx index 5de2a902c81..39f5719361b 100644 --- a/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx +++ b/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; import { fireEvent } from '@testing-library/dom'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; + import { MenuItem, MenuItemProps } from './MenuItem'; describe('MenuItem', () => { diff --git a/packages/grafana-ui/src/components/Menu/MenuItem.tsx b/packages/grafana-ui/src/components/Menu/MenuItem.tsx index 4a383214c2c..7f47ba10b92 100644 --- a/packages/grafana-ui/src/components/Menu/MenuItem.tsx +++ b/packages/grafana-ui/src/components/Menu/MenuItem.tsx @@ -1,11 +1,14 @@ -import React, { ReactElement, useCallback, useMemo, useState, useRef, useImperativeHandle } from 'react'; import { css, cx } from '@emotion/css'; +import React, { ReactElement, useCallback, useMemo, useState, useRef, useImperativeHandle } from 'react'; + import { GrafanaTheme2, LinkTarget } from '@grafana/data'; + import { useStyles2 } from '../../themes'; -import { Icon } from '../Icon/Icon'; -import { IconName } from '../../types'; -import { SubMenu } from './SubMenu'; import { getFocusStyles } from '../../themes/mixins'; +import { IconName } from '../../types'; +import { Icon } from '../Icon/Icon'; + +import { SubMenu } from './SubMenu'; /** @internal */ export type MenuItemElement = HTMLAnchorElement & HTMLButtonElement & HTMLDivElement; diff --git a/packages/grafana-ui/src/components/Menu/SubMenu.test.tsx b/packages/grafana-ui/src/components/Menu/SubMenu.test.tsx index b96064ffbfe..a7785fe8974 100644 --- a/packages/grafana-ui/src/components/Menu/SubMenu.test.tsx +++ b/packages/grafana-ui/src/components/Menu/SubMenu.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; + import { MenuItem } from './MenuItem'; import { SubMenu } from './SubMenu'; diff --git a/packages/grafana-ui/src/components/Menu/SubMenu.tsx b/packages/grafana-ui/src/components/Menu/SubMenu.tsx index cbdab1f25f6..dfab72235e3 100644 --- a/packages/grafana-ui/src/components/Menu/SubMenu.tsx +++ b/packages/grafana-ui/src/components/Menu/SubMenu.tsx @@ -1,12 +1,15 @@ -import React, { ReactElement, useRef } from 'react'; import { css } from '@emotion/css'; +import React, { ReactElement, useRef } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; + import { useStyles2 } from '../../themes'; import { Icon } from '../Icon/Icon'; + import { MenuItemProps } from './MenuItem'; -import { getPosition } from './utils'; import { useMenuFocus } from './hooks'; +import { getPosition } from './utils'; /** @internal */ export interface SubMenuProps { diff --git a/packages/grafana-ui/src/components/Menu/hooks.test.tsx b/packages/grafana-ui/src/components/Menu/hooks.test.tsx index 6c2203075d6..a35406e6a35 100644 --- a/packages/grafana-ui/src/components/Menu/hooks.test.tsx +++ b/packages/grafana-ui/src/components/Menu/hooks.test.tsx @@ -1,7 +1,8 @@ -import React, { createRef, KeyboardEvent, RefObject } from 'react'; -import { render, screen } from '@testing-library/react'; import { fireEvent } from '@testing-library/dom'; +import { render, screen } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react-hooks'; +import React, { createRef, KeyboardEvent, RefObject } from 'react'; + import { useMenuFocus } from './hooks'; describe('useMenuFocus', () => { diff --git a/packages/grafana-ui/src/components/Menu/hooks.ts b/packages/grafana-ui/src/components/Menu/hooks.ts index 1ea4525bad0..b21f220c302 100644 --- a/packages/grafana-ui/src/components/Menu/hooks.ts +++ b/packages/grafana-ui/src/components/Menu/hooks.ts @@ -1,5 +1,6 @@ import { RefObject, useEffect, useState } from 'react'; import { useEffectOnce } from 'react-use'; + import { MenuItemElement } from './MenuItem'; const modulo = (a: number, n: number) => ((a % n) + n) % n; diff --git a/packages/grafana-ui/src/components/Modal/Modal.story.tsx b/packages/grafana-ui/src/components/Modal/Modal.story.tsx index ee2e9e3bdee..e294d5f2479 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.story.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.story.tsx @@ -1,12 +1,14 @@ -import React, { useState } from 'react'; -import { oneLineTrim } from 'common-tags'; -import { Story, Meta } from '@storybook/react'; -import { getAvailableIcons } from '../../types'; -import { Button, Modal, ModalTabsHeader, TabContent } from '@grafana/ui'; import { css, cx } from '@emotion/css'; +import { Story, Meta } from '@storybook/react'; +import { oneLineTrim } from 'common-tags'; +import React, { useState } from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Button, Modal, ModalTabsHeader, TabContent } from '@grafana/ui'; + +import { getAvailableIcons } from '../../types'; import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './Modal.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Modal/Modal.test.tsx b/packages/grafana-ui/src/components/Modal/Modal.test.tsx index f629c8fb54d..08d558d395b 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.test.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { Modal } from './Modal'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { Modal } from './Modal'; describe('Modal', () => { it('renders nothing by default or when isOpen is false', () => { diff --git a/packages/grafana-ui/src/components/Modal/Modal.tsx b/packages/grafana-ui/src/components/Modal/Modal.tsx index a159a2825ec..8b2e6adc175 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.tsx @@ -1,7 +1,6 @@ import { cx } from '@emotion/css'; -import { FocusScope } from '@react-aria/focus'; import { useDialog } from '@react-aria/dialog'; - +import { FocusScope } from '@react-aria/focus'; import { OverlayContainer, useOverlay } from '@react-aria/overlays'; import React, { PropsWithChildren, useRef } from 'react'; @@ -9,8 +8,9 @@ import { useTheme2 } from '../../themes'; import { IconName } from '../../types'; import { IconButton } from '../IconButton/IconButton'; import { HorizontalGroup } from '../Layout/Layout'; -import { getModalStyles } from './getModalStyles'; + import { ModalHeader } from './ModalHeader'; +import { getModalStyles } from './getModalStyles'; export interface Props { /** @deprecated no longer used */ diff --git a/packages/grafana-ui/src/components/Modal/ModalHeader.tsx b/packages/grafana-ui/src/components/Modal/ModalHeader.tsx index 6d5c2c1acaf..3eeaaffdaf7 100644 --- a/packages/grafana-ui/src/components/Modal/ModalHeader.tsx +++ b/packages/grafana-ui/src/components/Modal/ModalHeader.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { getModalStyles } from './getModalStyles'; -import { IconName } from '../../types'; + import { useStyles2 } from '../../themes'; +import { IconName } from '../../types'; + +import { getModalStyles } from './getModalStyles'; interface Props { title: string; diff --git a/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx b/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx index 8b00f9c8f9a..1c1bbb13d76 100644 --- a/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx +++ b/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { IconName } from '../../types'; interface Props { diff --git a/packages/grafana-ui/src/components/Modal/ModalTabsHeader.tsx b/packages/grafana-ui/src/components/Modal/ModalTabsHeader.tsx index a4a2820ab2c..dd2c09e129d 100644 --- a/packages/grafana-ui/src/components/Modal/ModalTabsHeader.tsx +++ b/packages/grafana-ui/src/components/Modal/ModalTabsHeader.tsx @@ -1,8 +1,11 @@ import React from 'react'; + import { NavModelItem } from '@grafana/data'; + import { IconName } from '../../types'; -import { TabsBar } from '../Tabs/TabsBar'; import { Tab } from '../Tabs/Tab'; +import { TabsBar } from '../Tabs/TabsBar'; + import { ModalHeader } from './ModalHeader'; interface ModalTab { diff --git a/packages/grafana-ui/src/components/Modal/getModalStyles.ts b/packages/grafana-ui/src/components/Modal/getModalStyles.ts index 312c517baa0..470f0201ac6 100644 --- a/packages/grafana-ui/src/components/Modal/getModalStyles.ts +++ b/packages/grafana-ui/src/components/Modal/getModalStyles.ts @@ -1,5 +1,7 @@ import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; + import { stylesFactory } from '../../themes'; export const getModalStyles = stylesFactory((theme: GrafanaTheme2) => { diff --git a/packages/grafana-ui/src/components/Monaco/CodeEditor.story.internal.tsx b/packages/grafana-ui/src/components/Monaco/CodeEditor.story.internal.tsx index 507693571f0..9fd540a97dd 100644 --- a/packages/grafana-ui/src/components/Monaco/CodeEditor.story.internal.tsx +++ b/packages/grafana-ui/src/components/Monaco/CodeEditor.story.internal.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { Meta, Story } from '@storybook/react'; import { action } from '@storybook/addon-actions'; +import { Meta, Story } from '@storybook/react'; +import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import mdx from './CodeEditor.mdx'; + import { CodeEditor } from './CodeEditor'; +import mdx from './CodeEditor.mdx'; export default { title: 'CodeEditor', diff --git a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx index b2eef4196e2..c6f8dad7157 100644 --- a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx @@ -1,15 +1,16 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { ReactMonacoEditorLazy } from './ReactMonacoEditorLazy'; import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api'; -import { selectors } from '@grafana/e2e-selectors'; +import React from 'react'; + import { GrafanaTheme2, monacoLanguageRegistry } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { withTheme2 } from '../../themes'; import { Themeable2 } from '../../types'; -import { CodeEditorProps, Monaco, MonacoEditor as MonacoEditorType, MonacoOptions } from './types'; +import { ReactMonacoEditorLazy } from './ReactMonacoEditorLazy'; import { registerSuggestions } from './suggestions'; +import { CodeEditorProps, Monaco, MonacoEditor as MonacoEditorType, MonacoOptions } from './types'; type Props = CodeEditorProps & Themeable2; diff --git a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx index d0a00fa1172..b2efffd2d7b 100644 --- a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditor.tsx @@ -1,7 +1,9 @@ -import React, { useEffect } from 'react'; import MonacoEditor, { loader as monacoEditorLoader, useMonaco } from '@monaco-editor/react'; -import defineThemes from './theme'; +import React, { useEffect } from 'react'; + import { useTheme2 } from '../../themes'; + +import defineThemes from './theme'; import type { ReactMonacoEditorProps } from './types'; let initalized = false; diff --git a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditorLazy.tsx b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditorLazy.tsx index b8db38dac1c..5075f66242d 100644 --- a/packages/grafana-ui/src/components/Monaco/ReactMonacoEditorLazy.tsx +++ b/packages/grafana-ui/src/components/Monaco/ReactMonacoEditorLazy.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { useAsyncDependency } from '../../utils/useAsyncDependency'; + import { ErrorWithStack, LoadingPlaceholder } from '..'; +import { useAsyncDependency } from '../../utils/useAsyncDependency'; + // we only use import type so it will not be included in the bundle import type { ReactMonacoEditorProps } from './types'; diff --git a/packages/grafana-ui/src/components/Monaco/theme.ts b/packages/grafana-ui/src/components/Monaco/theme.ts index a85d79ac025..d073f40377e 100644 --- a/packages/grafana-ui/src/components/Monaco/theme.ts +++ b/packages/grafana-ui/src/components/Monaco/theme.ts @@ -1,4 +1,5 @@ import { GrafanaTheme2 } from '@grafana/data'; + import { Monaco, monacoTypes } from './types'; function getColors(theme?: GrafanaTheme2): monacoTypes.editor.IColors { diff --git a/packages/grafana-ui/src/components/Monaco/types.ts b/packages/grafana-ui/src/components/Monaco/types.ts index 876a941b02e..94ae00f245e 100644 --- a/packages/grafana-ui/src/components/Monaco/types.ts +++ b/packages/grafana-ui/src/components/Monaco/types.ts @@ -1,6 +1,6 @@ // We use `import type` to guarantee it'll be erased from the JS and it doesnt accidently bundle monaco -import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api'; import type { EditorProps } from '@monaco-editor/react'; +import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api'; // we do not allow customizing the theme. // (theme is complicated in Monaco, right now there is diff --git a/packages/grafana-ui/src/components/Monaco/utils.ts b/packages/grafana-ui/src/components/Monaco/utils.ts index c2f50ab19fa..3dbf207064a 100644 --- a/packages/grafana-ui/src/components/Monaco/utils.ts +++ b/packages/grafana-ui/src/components/Monaco/utils.ts @@ -1,4 +1,5 @@ import { VariableSuggestion } from '@grafana/data'; + import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind } from './types'; /** diff --git a/packages/grafana-ui/src/components/OptionsUI/color.tsx b/packages/grafana-ui/src/components/OptionsUI/color.tsx index 3b182dfd907..216591410d5 100644 --- a/packages/grafana-ui/src/components/OptionsUI/color.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/color.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; -import { ColorPicker } from '../ColorPicker/ColorPicker'; -import { useTheme2, useStyles2 } from '../../themes'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { useTheme2, useStyles2 } from '../../themes'; +import { ColorPicker } from '../ColorPicker/ColorPicker'; import { ColorSwatch } from '../ColorPicker/ColorSwatch'; /** diff --git a/packages/grafana-ui/src/components/OptionsUI/fieldColor.tsx b/packages/grafana-ui/src/components/OptionsUI/fieldColor.tsx index 21b1f4f24da..41e6d0fdb61 100644 --- a/packages/grafana-ui/src/components/OptionsUI/fieldColor.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/fieldColor.tsx @@ -1,4 +1,6 @@ +import { css } from '@emotion/css'; import React, { CSSProperties, FC } from 'react'; + import { FieldConfigEditorProps, FieldColorModeId, @@ -11,12 +13,13 @@ import { FieldColorSeriesByMode, getFieldColorMode, } from '@grafana/data'; -import { Select } from '../Select/Select'; -import { ColorValueEditor } from './color'; + import { useStyles2, useTheme2 } from '../../themes/ThemeContext'; -import { css } from '@emotion/css'; import { Field } from '../Forms/Field'; import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup'; +import { Select } from '../Select/Select'; + +import { ColorValueEditor } from './color'; export const FieldColorEditor: React.FC> = ({ value, diff --git a/packages/grafana-ui/src/components/OptionsUI/links.tsx b/packages/grafana-ui/src/components/OptionsUI/links.tsx index 844b9e75534..9501e9a0b7a 100644 --- a/packages/grafana-ui/src/components/OptionsUI/links.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/links.tsx @@ -1,10 +1,12 @@ import React from 'react'; + import { DataLink, DataLinksFieldConfigSettings, FieldConfigEditorProps, VariableSuggestionsScope, } from '@grafana/data'; + import { DataLinksInlineEditor } from '../DataLinks/DataLinksInlineEditor/DataLinksInlineEditor'; export const DataLinksValueEditor: React.FC> = ({ diff --git a/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx b/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx index c6d45a53d87..5970d7cddfe 100644 --- a/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/multiSelect.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { FieldConfigEditorProps, SelectFieldConfigSettings, SelectableValue } from '@grafana/data'; + import { MultiSelect } from '../Select/Select'; interface State { diff --git a/packages/grafana-ui/src/components/OptionsUI/number.tsx b/packages/grafana-ui/src/components/OptionsUI/number.tsx index a2741d7f1d5..8d14073c517 100644 --- a/packages/grafana-ui/src/components/OptionsUI/number.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/number.tsx @@ -1,10 +1,12 @@ import React, { useCallback } from 'react'; + import { FieldConfigEditorProps, toIntegerOrUndefined, toFloatOrUndefined, NumberFieldConfigSettings, } from '@grafana/data'; + import { Input } from '../Input/Input'; export const NumberValueEditor: React.FC> = ({ diff --git a/packages/grafana-ui/src/components/OptionsUI/select.tsx b/packages/grafana-ui/src/components/OptionsUI/select.tsx index 2c3405bbcc0..2737963faf9 100644 --- a/packages/grafana-ui/src/components/OptionsUI/select.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/select.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { FieldConfigEditorProps, SelectFieldConfigSettings, SelectableValue } from '@grafana/data'; + import { Select } from '../Select/Select'; interface State { diff --git a/packages/grafana-ui/src/components/OptionsUI/slider.tsx b/packages/grafana-ui/src/components/OptionsUI/slider.tsx index 56a7699a3dd..ea2af1014db 100644 --- a/packages/grafana-ui/src/components/OptionsUI/slider.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/slider.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { FieldConfigEditorProps, SliderFieldConfigSettings } from '@grafana/data'; + import { Slider } from '../Slider/Slider'; export const SliderValueEditor: React.FC> = ({ diff --git a/packages/grafana-ui/src/components/OptionsUI/stats.tsx b/packages/grafana-ui/src/components/OptionsUI/stats.tsx index 01bdca1c455..146a1371e4e 100644 --- a/packages/grafana-ui/src/components/OptionsUI/stats.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/stats.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { FieldConfigEditorProps, StatsPickerConfigSettings } from '@grafana/data'; + import { StatsPicker } from '../StatsPicker/StatsPicker'; export const StatsPickerEditor: React.FC> = ({ diff --git a/packages/grafana-ui/src/components/OptionsUI/string.tsx b/packages/grafana-ui/src/components/OptionsUI/string.tsx index c8f9db7412c..7e22b318db6 100644 --- a/packages/grafana-ui/src/components/OptionsUI/string.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/string.tsx @@ -1,5 +1,7 @@ import React, { useCallback } from 'react'; + import { StandardEditorProps, StringFieldConfigSettings } from '@grafana/data'; + import { Input } from '../Input/Input'; import { TextArea } from '../TextArea/TextArea'; diff --git a/packages/grafana-ui/src/components/OptionsUI/strings.tsx b/packages/grafana-ui/src/components/OptionsUI/strings.tsx index 9e1098818a6..4b5c55e3c84 100644 --- a/packages/grafana-ui/src/components/OptionsUI/strings.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/strings.tsx @@ -1,10 +1,12 @@ -import React from 'react'; -import { FieldConfigEditorProps, StringFieldConfigSettings, GrafanaTheme } from '@grafana/data'; -import { Input } from '../Input/Input'; -import { Icon } from '../Icon/Icon'; -import { stylesFactory, getTheme } from '../../themes'; import { css } from '@emotion/css'; +import React from 'react'; + +import { FieldConfigEditorProps, StringFieldConfigSettings, GrafanaTheme } from '@grafana/data'; + +import { stylesFactory, getTheme } from '../../themes'; import { Button } from '../Button'; +import { Icon } from '../Icon/Icon'; +import { Input } from '../Input/Input'; type Props = FieldConfigEditorProps; interface State { diff --git a/packages/grafana-ui/src/components/OptionsUI/units.tsx b/packages/grafana-ui/src/components/OptionsUI/units.tsx index 6431f49de00..695681649fc 100644 --- a/packages/grafana-ui/src/components/OptionsUI/units.tsx +++ b/packages/grafana-ui/src/components/OptionsUI/units.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { FieldConfigEditorProps, UnitFieldConfigSettings } from '@grafana/data'; + import { UnitPicker } from '../UnitPicker/UnitPicker'; export const UnitValueEditor: React.FC> = ({ diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx index 502cabf497d..f1ee83d2ed1 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import { ToolbarButton, VerticalGroup } from '@grafana/ui'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { PageToolbar } from './PageToolbar'; -import { StoryExample } from '../../utils/storybook/StoryExample'; import { action } from '@storybook/addon-actions'; +import React from 'react'; + +import { ToolbarButton, VerticalGroup } from '@grafana/ui'; + +import { StoryExample } from '../../utils/storybook/StoryExample'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { IconButton } from '../IconButton/IconButton'; +import { PageToolbar } from './PageToolbar'; + export default { title: 'Layout/PageToolbar', component: PageToolbar, diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx index 07c02c3bc65..7fbc8336df9 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -1,14 +1,16 @@ -import React, { FC, ReactNode } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { Link } from '..'; +import { styleMixins } from '../../themes'; import { useStyles2 } from '../../themes/ThemeContext'; +import { getFocusStyles } from '../../themes/mixins'; import { IconName } from '../../types'; import { Icon } from '../Icon/Icon'; -import { styleMixins } from '../../themes'; import { IconButton } from '../IconButton/IconButton'; -import { selectors } from '@grafana/e2e-selectors'; -import { Link } from '..'; -import { getFocusStyles } from '../../themes/mixins'; export interface Props { pageIcon?: IconName; diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx index fca8d762155..80192bfee0e 100644 --- a/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx +++ b/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx @@ -1,9 +1,12 @@ -import React, { useState } from 'react'; import { Story } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React, { useState } from 'react'; + import { Pagination } from '@grafana/ui'; -import mdx from './Pagination.mdx'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { Props } from './Pagination'; +import mdx from './Pagination.mdx'; export default { title: 'Buttons/Pagination', diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.test.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.test.tsx index 305e25050a3..68a85e6746c 100644 --- a/packages/grafana-ui/src/components/Pagination/Pagination.test.tsx +++ b/packages/grafana-ui/src/components/Pagination/Pagination.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { Pagination } from './Pagination'; describe('Pagination component', () => { diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.tsx index a15b7a1fe24..df605c60156 100644 --- a/packages/grafana-ui/src/components/Pagination/Pagination.tsx +++ b/packages/grafana-ui/src/components/Pagination/Pagination.tsx @@ -1,5 +1,6 @@ -import React, { useMemo } from 'react'; import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; + import { useStyles2 } from '../../themes'; import { Button, ButtonVariant } from '../Button'; import { Icon } from '../Icon/Icon'; diff --git a/packages/grafana-ui/src/components/PanelChrome/ErrorIndicator.tsx b/packages/grafana-ui/src/components/PanelChrome/ErrorIndicator.tsx index 44a74d1d37e..b2ebb0860eb 100644 --- a/packages/grafana-ui/src/components/PanelChrome/ErrorIndicator.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/ErrorIndicator.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme } from '@grafana/data'; + +import { useStyles } from '../../themes'; import { Icon } from '../Icon/Icon'; import { Tooltip } from '../Tooltip/Tooltip'; -import { useStyles } from '../../themes'; -import { GrafanaTheme } from '@grafana/data'; /** * @internal diff --git a/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx b/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx index 92bd024b6de..c5999d19f07 100644 --- a/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/LoadingIndicator.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; + +import { useStyles } from '../../themes'; import { Icon } from '../Icon/Icon'; import { Tooltip } from '../Tooltip/Tooltip'; -import { useStyles } from '../../themes'; /** * @internal diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx index dab30b5d7c6..a14a0ffed96 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.story.tsx @@ -1,11 +1,13 @@ -import React, { CSSProperties, useState } from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { PanelChrome, useTheme, PanelChromeProps } from '@grafana/ui'; -import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; import { merge } from 'lodash'; -import { GrafanaTheme } from '@grafana/data'; +import React, { CSSProperties, useState } from 'react'; import { useInterval } from 'react-use'; +import { GrafanaTheme } from '@grafana/data'; +import { PanelChrome, useTheme, PanelChromeProps } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; + export default { title: 'Visualizations/PanelChrome', component: PanelChrome, diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index ab053e5ff1f..1b759ac826f 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -1,8 +1,10 @@ -import React, { CSSProperties, ReactNode } from 'react'; import { css } from '@emotion/css'; -import { useStyles2, useTheme2 } from '../../themes'; +import React, { CSSProperties, ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2, useTheme2 } from '../../themes'; + /** * @internal */ diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts b/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts index fbbf77f9dba..692b4e7d76c 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts +++ b/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts @@ -1,3 +1,5 @@ +import React from 'react'; + import { EventBusSrv, EventBus, @@ -7,7 +9,7 @@ import { SplitOpen, CoreApp, } from '@grafana/data'; -import React from 'react'; + import { SeriesVisibilityChangeMode } from '.'; /** @alpha */ diff --git a/packages/grafana-ui/src/components/PanelChrome/index.ts b/packages/grafana-ui/src/components/PanelChrome/index.ts index 34b9286b246..db314257e28 100644 --- a/packages/grafana-ui/src/components/PanelChrome/index.ts +++ b/packages/grafana-ui/src/components/PanelChrome/index.ts @@ -1,6 +1,7 @@ import React from 'react'; -import { LoadingIndicator } from './LoadingIndicator'; + import { ErrorIndicator } from './ErrorIndicator'; +import { LoadingIndicator } from './LoadingIndicator'; import { PanelChrome as PanelChromeComponent, PanelChromeProps } from './PanelChrome'; /** diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx index 4c92e79a7fa..0f35ecf1404 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.story.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { Story } from '@storybook/react'; -import { PluginSignatureBadge } from '@grafana/ui'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React from 'react'; + import { PluginSignatureStatus } from '@grafana/data'; +import { PluginSignatureBadge } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; export default { title: 'Data Display/PluginSignatureBadge', diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx index d237060127e..6913310a7df 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx @@ -1,5 +1,7 @@ import React, { HTMLAttributes } from 'react'; + import { PluginSignatureStatus } from '@grafana/data'; + import { Badge, BadgeProps } from '../Badge/Badge'; /** diff --git a/packages/grafana-ui/src/components/Portal/Portal.tsx b/packages/grafana-ui/src/components/Portal/Portal.tsx index a5397721eba..0d260cca338 100644 --- a/packages/grafana-ui/src/components/Portal/Portal.tsx +++ b/packages/grafana-ui/src/components/Portal/Portal.tsx @@ -1,5 +1,6 @@ import React, { PropsWithChildren, useLayoutEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; + import { useTheme2 } from '../../themes'; interface Props { diff --git a/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx b/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx index de2ffe3e60e..d7bae075ae9 100644 --- a/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx +++ b/packages/grafana-ui/src/components/QueryField/QueryField.story.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { QueryField } from '@grafana/ui'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + export default { title: 'Data Source/QueryField', component: QueryField, diff --git a/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx b/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx index fdfee01c17f..0ce32772ca0 100644 --- a/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx +++ b/packages/grafana-ui/src/components/QueryField/QueryField.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { UnThemedQueryField } from './QueryField'; +import React from 'react'; import { Editor } from 'slate'; + import { createTheme } from '@grafana/data'; +import { UnThemedQueryField } from './QueryField'; + describe('', () => { it('should render with null initial value', () => { const wrapper = shallow( diff --git a/packages/grafana-ui/src/components/QueryField/QueryField.tsx b/packages/grafana-ui/src/components/QueryField/QueryField.tsx index bf22dac6fe0..e3ddca39eb4 100644 --- a/packages/grafana-ui/src/components/QueryField/QueryField.tsx +++ b/packages/grafana-ui/src/components/QueryField/QueryField.tsx @@ -1,20 +1,13 @@ +import { css, cx } from '@emotion/css'; +import classnames from 'classnames'; import { debounce } from 'lodash'; import React, { Context } from 'react'; - import { Value, Editor as CoreEditor } from 'slate'; -import { Editor, Plugin } from '@grafana/slate-react'; import Plain from 'slate-plain-serializer'; -import classnames from 'classnames'; -import { - ClearPlugin, - NewlinePlugin, - SelectionShortcutsPlugin, - IndentationPlugin, - ClipboardPlugin, - RunnerPlugin, - SuggestionsPlugin, -} from '../../slate-plugins'; +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Editor, Plugin } from '@grafana/slate-react'; import { makeValue, @@ -25,9 +18,15 @@ import { SuggestionsState, Themeable2, } from '../..'; -import { selectors } from '@grafana/e2e-selectors'; -import { css, cx } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { + ClearPlugin, + NewlinePlugin, + SelectionShortcutsPlugin, + IndentationPlugin, + ClipboardPlugin, + RunnerPlugin, + SuggestionsPlugin, +} from '../../slate-plugins'; import { withTheme2 } from '../../themes'; import { getFocusStyles } from '../../themes/mixins'; diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.story.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.story.tsx index c18c2663402..1ec9a5bafb3 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.story.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.story.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { UseState } from '../../utils/storybook/UseState'; +import React from 'react'; + import { RefreshPicker } from '@grafana/ui'; -import { StoryExample } from '../../utils/storybook/StoryExample'; + import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; +import { StoryExample } from '../../utils/storybook/StoryExample'; +import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { HorizontalGroup } from '../Layout/Layout'; export default { diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index 2ceeaa18023..2e437b0076b 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -1,10 +1,12 @@ -import React, { PureComponent } from 'react'; import formatDuration from 'date-fns/formatDuration'; +import React, { PureComponent } from 'react'; + import { SelectableValue, parseDuration } from '@grafana/data'; -import { ButtonSelect } from '../Dropdown/ButtonSelect'; -import { ButtonGroup, ToolbarButton, ToolbarButtonVariant } from '../Button'; import { selectors } from '@grafana/e2e-selectors'; +import { ButtonGroup, ToolbarButton, ToolbarButtonVariant } from '../Button'; +import { ButtonSelect } from '../Dropdown/ButtonSelect'; + // Default intervals used in the refresh picker component export const defaultIntervals = ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d']; const offLabel = 'Auto refresh turned off. Choose refresh time interval'; diff --git a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.story.internal.tsx b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.story.internal.tsx index 82c14a89d48..b9666246562 100644 --- a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.story.internal.tsx +++ b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.story.internal.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import { UseState } from '../../utils/storybook/UseState'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { SecretFormField, Props } from './SecretFormField'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { UseState } from '../../utils/storybook/UseState'; export default { title: 'Forms/SecretFormField', diff --git a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx index d71dc7a68e4..27da639c5ff 100644 --- a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx +++ b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx @@ -1,8 +1,9 @@ +import { css, cx } from '@emotion/css'; import { omit } from 'lodash'; import React, { InputHTMLAttributes, FunctionComponent } from 'react'; -import { FormField } from '../FormField/FormField'; + import { Button } from '../Button/Button'; -import { css, cx } from '@emotion/css'; +import { FormField } from '../FormField/FormField'; import { PopoverContent } from '../Tooltip'; export interface Props extends Omit, 'onReset'> { diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx index 60da75ef7de..e19028188fc 100644 --- a/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx @@ -1,8 +1,10 @@ -import React, { useState, ChangeEvent } from 'react'; import { Story, Meta } from '@storybook/react'; -import { SecretInput, Props } from './SecretInput'; +import React, { useState, ChangeEvent } from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { SecretInput, Props } from './SecretInput'; + export default { title: 'Forms/SecretInput', component: SecretInput, diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx index 010ed2fdf59..284b30fbf35 100644 --- a/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { SecretInput, RESET_BUTTON_TEXT, CONFIGURED_TEXT } from './SecretInput'; const PLACEHOLDER_TEXT = 'Your secret...'; @@ -33,7 +34,7 @@ describe('', () => { expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument(); }); - it('should be possible to reset a configured secret', () => { + it('should be possible to reset a configured secret', async () => { const onReset = jest.fn(); render( {}} onReset={onReset} placeholder={PLACEHOLDER_TEXT} />); @@ -43,12 +44,12 @@ describe('', () => { expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument(); // Click on "Reset" - userEvent.click(screen.getByRole('button', { name: RESET_BUTTON_TEXT })); + await userEvent.click(screen.getByRole('button', { name: RESET_BUTTON_TEXT })); expect(onReset).toHaveBeenCalledTimes(1); }); - it('should be possible to change the value of the secret', () => { + it('should be possible to change the value of the secret', async () => { const onChange = jest.fn(); render( {}} placeholder={PLACEHOLDER_TEXT} />); @@ -57,7 +58,7 @@ describe('', () => { expect(input).toHaveValue(''); - userEvent.type(input, 'Foo'); + await userEvent.type(input, 'Foo'); expect(onChange).toHaveBeenCalled(); expect(input).toHaveValue('Foo'); diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx index d46a70903a1..b996cf70236 100644 --- a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; + +import { Button } from '../Button'; import { Input } from '../Input/Input'; import { HorizontalGroup } from '../Layout/Layout'; -import { Button } from '../Button'; export type Props = React.ComponentProps & { /** TRUE if the secret was already configured. (It is needed as often the backend doesn't send back the actual secret, only the information that it was configured) */ diff --git a/packages/grafana-ui/src/components/Segment/Segment.story.tsx b/packages/grafana-ui/src/components/Segment/Segment.story.tsx index ab629544ce8..663753c26c9 100644 --- a/packages/grafana-ui/src/components/Segment/Segment.story.tsx +++ b/packages/grafana-ui/src/components/Segment/Segment.story.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; +import React, { useState } from 'react'; + import { Segment, Icon, SegmentSection } from '@grafana/ui'; const AddButton = ( diff --git a/packages/grafana-ui/src/components/Segment/Segment.tsx b/packages/grafana-ui/src/components/Segment/Segment.tsx index 23fa86e7f5a..c5eb15e91cf 100644 --- a/packages/grafana-ui/src/components/Segment/Segment.tsx +++ b/packages/grafana-ui/src/components/Segment/Segment.tsx @@ -1,11 +1,15 @@ -import React, { HTMLProps } from 'react'; import { cx } from '@emotion/css'; import { isObject } from 'lodash'; +import React, { HTMLProps } from 'react'; + import { SelectableValue } from '@grafana/data'; -import { SegmentSelect, useExpandableLabel, SegmentProps } from './'; -import { getSegmentStyles } from './styles'; -import { InlineLabel } from '../Forms/InlineLabel'; + import { useStyles } from '../../themes'; +import { InlineLabel } from '../Forms/InlineLabel'; + +import { getSegmentStyles } from './styles'; + +import { SegmentSelect, useExpandableLabel, SegmentProps } from './'; export interface SegmentSyncProps extends SegmentProps, Omit, 'value' | 'onChange'> { value?: T | SelectableValue; diff --git a/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx b/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx index 0c720b7350c..409f952c3cf 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx @@ -1,6 +1,7 @@ +import { action } from '@storybook/addon-actions'; import React, { useState } from 'react'; import { AsyncState } from 'react-use/lib/useAsync'; -import { action } from '@storybook/addon-actions'; + import { SelectableValue } from '@grafana/data'; import { SegmentAsync, Icon, SegmentSection } from '@grafana/ui'; diff --git a/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx b/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx index b952c38367e..cfdf16c6c5d 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx @@ -1,14 +1,18 @@ -import React, { HTMLProps } from 'react'; import { cx } from '@emotion/css'; import { isObject } from 'lodash'; -import { SegmentSelect } from './SegmentSelect'; -import { SelectableValue } from '@grafana/data'; -import { useExpandableLabel, SegmentProps } from '.'; +import React, { HTMLProps } from 'react'; import { useAsyncFn } from 'react-use'; import { AsyncState } from 'react-use/lib/useAsync'; -import { getSegmentStyles } from './styles'; -import { InlineLabel } from '../Forms/InlineLabel'; + +import { SelectableValue } from '@grafana/data'; + import { useStyles } from '../../themes'; +import { InlineLabel } from '../Forms/InlineLabel'; + +import { SegmentSelect } from './SegmentSelect'; +import { getSegmentStyles } from './styles'; + +import { useExpandableLabel, SegmentProps } from '.'; export interface SegmentAsyncProps extends SegmentProps, Omit, 'value' | 'onChange'> { value?: T | SelectableValue; diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx index af21609e1df..a7eb9ba2d51 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; +import React, { useState } from 'react'; + import { SegmentInput, Icon, SegmentSection } from '@grafana/ui'; const SegmentFrame = ({ children }: any) => ( diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx index 286c4b25288..048e439cb7b 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx @@ -1,11 +1,14 @@ -import React, { HTMLProps, useRef, useState } from 'react'; import { cx, css } from '@emotion/css'; +import React, { HTMLProps, useRef, useState } from 'react'; import useClickAway from 'react-use/lib/useClickAway'; -import { measureText } from '../../utils/measureText'; -import { useExpandableLabel, SegmentProps } from '.'; -import { getSegmentStyles } from './styles'; -import { InlineLabel } from '../Forms/InlineLabel'; + import { useStyles } from '../../themes'; +import { measureText } from '../../utils/measureText'; +import { InlineLabel } from '../Forms/InlineLabel'; + +import { getSegmentStyles } from './styles'; + +import { useExpandableLabel, SegmentProps } from '.'; export interface SegmentInputProps extends SegmentProps, Omit, 'value' | 'onChange'> { value: string | number; diff --git a/packages/grafana-ui/src/components/Segment/SegmentSection.tsx b/packages/grafana-ui/src/components/Segment/SegmentSection.tsx index a724c310391..03c13b77fff 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentSection.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentSection.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; -import { InlineLabel } from '../Forms/InlineLabel'; import { InlineFieldRow } from '../Forms/InlineFieldRow'; +import { InlineLabel } from '../Forms/InlineLabel'; /** * Horizontal section for editor components. diff --git a/packages/grafana-ui/src/components/Segment/SegmentSelect.tsx b/packages/grafana-ui/src/components/Segment/SegmentSelect.tsx index a50d59b4285..6d6a1fed373 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentSelect.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentSelect.tsx @@ -1,7 +1,9 @@ import React, { HTMLProps, useRef } from 'react'; + import { SelectableValue } from '@grafana/data'; -import { AsyncSelect, Select } from '../Select/Select'; + import { useTheme2 } from '../../themes/ThemeContext'; +import { AsyncSelect, Select } from '../Select/Select'; /** @internal * Should be used only internally by Segment/SegmentAsync which can guarantee that SegmentSelect is hidden diff --git a/packages/grafana-ui/src/components/Segment/styles.ts b/packages/grafana-ui/src/components/Segment/styles.ts index ad71e9ed31a..fab1d691a15 100644 --- a/packages/grafana-ui/src/components/Segment/styles.ts +++ b/packages/grafana-ui/src/components/Segment/styles.ts @@ -1,6 +1,7 @@ -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import { GrafanaTheme } from '@grafana/data'; + export const getSegmentStyles = (theme: GrafanaTheme) => { return { segment: css` diff --git a/packages/grafana-ui/src/components/Select/Container.tsx b/packages/grafana-ui/src/components/Select/Container.tsx index 97095b3d401..33eacc870bd 100644 --- a/packages/grafana-ui/src/components/Select/Container.tsx +++ b/packages/grafana-ui/src/components/Select/Container.tsx @@ -1,12 +1,14 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; +import { components, ContainerProps, GroupBase } from 'react-select'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory } from '../../themes'; import { useTheme2 } from '../../themes/ThemeContext'; +import { focusCss } from '../../themes/mixins'; import { sharedInputStyle } from '../Forms/commonStyles'; import { getInputStyles } from '../Input/Input'; -import { css, cx } from '@emotion/css'; -import { stylesFactory } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; -import { focusCss } from '../../themes/mixins'; -import { components, ContainerProps, GroupBase } from 'react-select'; export const SelectContainer = >( props: ContainerProps & { isFocused: boolean } diff --git a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx index 0da7adfd403..0a9fc619deb 100644 --- a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx +++ b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Icon } from '../Icon/Icon'; interface DropdownIndicatorProps { diff --git a/packages/grafana-ui/src/components/Select/IndicatorsContainer.tsx b/packages/grafana-ui/src/components/Select/IndicatorsContainer.tsx index 35ef549b262..010e028c0c5 100644 --- a/packages/grafana-ui/src/components/Select/IndicatorsContainer.tsx +++ b/packages/grafana-ui/src/components/Select/IndicatorsContainer.tsx @@ -1,7 +1,8 @@ +import { cx, css } from '@emotion/css'; import React from 'react'; + import { useTheme2 } from '../../themes/ThemeContext'; import { getInputStyles } from '../Input/Input'; -import { cx, css } from '@emotion/css'; export const IndicatorsContainer = React.forwardRef>((props, ref) => { const { children } = props; diff --git a/packages/grafana-ui/src/components/Select/InputControl.tsx b/packages/grafana-ui/src/components/Select/InputControl.tsx index d06a268ebc7..c41f466163f 100644 --- a/packages/grafana-ui/src/components/Select/InputControl.tsx +++ b/packages/grafana-ui/src/components/Select/InputControl.tsx @@ -1,10 +1,12 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory } from '../../themes'; import { useTheme2 } from '../../themes/ThemeContext'; import { inputPadding } from '../Forms/commonStyles'; import { getInputStyles } from '../Input/Input'; -import { css, cx } from '@emotion/css'; -import { stylesFactory } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; interface InputControlProps { /** Show an icon as a prefix in the input */ diff --git a/packages/grafana-ui/src/components/Select/MultiValue.tsx b/packages/grafana-ui/src/components/Select/MultiValue.tsx index c7166459e68..0a02abc316a 100644 --- a/packages/grafana-ui/src/components/Select/MultiValue.tsx +++ b/packages/grafana-ui/src/components/Select/MultiValue.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useTheme2 } from '../../themes'; import { IconButton } from '../IconButton/IconButton'; + import { getSelectStyles } from './getSelectStyles'; interface MultiValueContainerProps { diff --git a/packages/grafana-ui/src/components/Select/Select.story.tsx b/packages/grafana-ui/src/components/Select/Select.story.tsx index 9ce878c5972..5466c26ce73 100644 --- a/packages/grafana-ui/src/components/Select/Select.story.tsx +++ b/packages/grafana-ui/src/components/Select/Select.story.tsx @@ -1,14 +1,17 @@ -import React, { useState } from 'react'; -import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { SelectableValue } from '@grafana/data'; -import { Icon, Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from '@grafana/ui'; -import { getAvailableIcons, IconName } from '../../types'; -import { SelectCommonProps } from './types'; -import { Meta, Story } from '@storybook/react'; -import { generateOptions } from './mockOptions'; -import mdx from './Select.mdx'; import { auto } from '@popperjs/core'; import { action } from '@storybook/addon-actions'; +import { Meta, Story } from '@storybook/react'; +import React, { useState } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { Icon, Select, AsyncSelect, MultiSelect, AsyncMultiSelect } from '@grafana/ui'; + +import { getAvailableIcons, IconName } from '../../types'; +import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import mdx from './Select.mdx'; +import { generateOptions } from './mockOptions'; +import { SelectCommonProps } from './types'; export default { title: 'Forms/Select', diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index ad94830f156..0dc8310c474 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -1,8 +1,10 @@ import React from 'react'; + import { SelectableValue } from '@grafana/data'; -import { SelectCommonProps, MultiSelectCommonProps, SelectAsyncProps } from './types'; + import { SelectBase } from './SelectBase'; import { SelectContainer, SelectContainerProps } from './SelectContainer'; +import { SelectCommonProps, MultiSelectCommonProps, SelectAsyncProps } from './types'; export function Select(props: SelectCommonProps) { return ; diff --git a/packages/grafana-ui/src/components/Select/SelectBase.test.tsx b/packages/grafana-ui/src/components/Select/SelectBase.test.tsx index 81033a3de78..26b8b20cb4a 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.test.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.test.tsx @@ -1,9 +1,11 @@ -import React, { useState } from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { selectOptionInTest } from './test-utils'; +import React, { useState } from 'react'; + import { SelectableValue } from '@grafana/data'; + import { SelectBase } from './SelectBase'; +import { selectOptionInTest } from './test-utils'; describe('SelectBase', () => { const onChangeHandler = () => jest.fn(); @@ -22,9 +24,9 @@ describe('SelectBase', () => { render(); }); - it('renders empty options information', () => { + it('renders empty options information', async () => { render(); - userEvent.click(screen.getByText(/choose/i)); + await userEvent.click(screen.getByText(/choose/i)); expect(screen.queryByText(/no options found/i)).toBeVisible(); }); @@ -54,7 +56,7 @@ describe('SelectBase', () => { render(); expect(screen.queryByText('Test label')).toBeInTheDocument(); - userEvent.click(screen.getByText('clear value')); + await userEvent.click(screen.getByText('clear value')); expect(screen.queryByText('Test label')).not.toBeInTheDocument(); }); @@ -186,9 +188,9 @@ describe('SelectBase', () => { }); describe('options', () => { - it('renders menu with provided options', () => { + it('renders menu with provided options', async () => { render(); - userEvent.click(screen.getByText(/choose/i)); + await userEvent.click(screen.getByText(/choose/i)); const menuOptions = screen.getAllByLabelText('Select option'); expect(menuOptions).toHaveLength(2); }); diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index b74dc5258d0..d6ab426a6cb 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -1,25 +1,26 @@ import React, { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; import { default as ReactSelect } from 'react-select'; -import Creatable from 'react-select/creatable'; import { default as ReactAsyncSelect } from 'react-select/async'; import { default as AsyncCreatable } from 'react-select/async-creatable'; +import Creatable from 'react-select/creatable'; +import { useTheme2 } from '../../themes'; import { Icon } from '../Icon/Icon'; import { Spinner } from '../Spinner/Spinner'; -import { useCustomSelectStyles } from './resetSelectStyles'; -import { SelectMenu, SelectMenuOptions } from './SelectMenu'; -import { IndicatorsContainer } from './IndicatorsContainer'; -import { ValueContainer } from './ValueContainer'; -import { InputControl } from './InputControl'; -import { SelectContainer } from './SelectContainer'; + import { DropdownIndicator } from './DropdownIndicator'; +import { IndicatorsContainer } from './IndicatorsContainer'; +import { InputControl } from './InputControl'; +import { MultiValueContainer, MultiValueRemove } from './MultiValue'; +import { SelectContainer } from './SelectContainer'; +import { SelectMenu, SelectMenuOptions } from './SelectMenu'; import { SelectOptionGroup } from './SelectOptionGroup'; import { SingleValue } from './SingleValue'; -import { MultiValueContainer, MultiValueRemove } from './MultiValue'; -import { useTheme2 } from '../../themes'; +import { ValueContainer } from './ValueContainer'; import { getSelectStyles } from './getSelectStyles'; -import { cleanValue, findSelectedValue } from './utils'; +import { useCustomSelectStyles } from './resetSelectStyles'; import { ActionMeta, SelectBaseProps, SelectValue } from './types'; +import { cleanValue, findSelectedValue } from './utils'; interface ExtraValuesIndicatorProps { maxVisibleValues?: number | undefined; diff --git a/packages/grafana-ui/src/components/Select/SelectContainer.tsx b/packages/grafana-ui/src/components/Select/SelectContainer.tsx index 6fdda6e9807..c7d500e9706 100644 --- a/packages/grafana-ui/src/components/Select/SelectContainer.tsx +++ b/packages/grafana-ui/src/components/Select/SelectContainer.tsx @@ -1,12 +1,14 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; +import { components, ContainerProps as BaseContainerProps, GroupBase } from 'react-select'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory } from '../../themes'; import { useTheme2 } from '../../themes/ThemeContext'; +import { focusCss } from '../../themes/mixins'; import { sharedInputStyle } from '../Forms/commonStyles'; import { getInputStyles } from '../Input/Input'; -import { css, cx } from '@emotion/css'; -import { stylesFactory } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; -import { focusCss } from '../../themes/mixins'; -import { components, ContainerProps as BaseContainerProps, GroupBase } from 'react-select'; // isFocus prop is actually available, but its not in the types for the version we have. export interface SelectContainerProps> diff --git a/packages/grafana-ui/src/components/Select/SelectMenu.tsx b/packages/grafana-ui/src/components/Select/SelectMenu.tsx index d01197e3c71..d3e9c7c4352 100644 --- a/packages/grafana-ui/src/components/Select/SelectMenu.tsx +++ b/packages/grafana-ui/src/components/Select/SelectMenu.tsx @@ -1,11 +1,14 @@ -import React, { FC, RefCallback } from 'react'; -import { useTheme2 } from '../../themes/ThemeContext'; -import { getSelectStyles } from './getSelectStyles'; import { cx } from '@emotion/css'; +import React, { FC, RefCallback } from 'react'; + import { SelectableValue } from '@grafana/data'; + +import { useTheme2 } from '../../themes/ThemeContext'; +import { IconName } from '../../types'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; import { Icon } from '../Icon/Icon'; -import { IconName } from '../../types'; + +import { getSelectStyles } from './getSelectStyles'; interface SelectMenuProps { maxHeight: number; diff --git a/packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx b/packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx index 01697dd9a56..5d7ec5a2f24 100644 --- a/packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx +++ b/packages/grafana-ui/src/components/Select/SelectOptionGroup.tsx @@ -1,7 +1,9 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; +import React, { PureComponent } from 'react'; import { GroupProps } from 'react-select'; + +import { GrafanaTheme } from '@grafana/data'; + import { stylesFactory, withTheme } from '../../themes'; import { Themeable } from '../../types'; import { Icon } from '../Icon/Icon'; diff --git a/packages/grafana-ui/src/components/Select/SingleValue.tsx b/packages/grafana-ui/src/components/Select/SingleValue.tsx index 8ae91d4ab4c..785978ad195 100644 --- a/packages/grafana-ui/src/components/Select/SingleValue.tsx +++ b/packages/grafana-ui/src/components/Select/SingleValue.tsx @@ -1,14 +1,16 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; import { components, GroupBase, SingleValueProps } from 'react-select'; -import { useDelayedSwitch } from '../../utils/useDelayedSwitch'; -import { useStyles2 } from '../../themes'; -import { SlideOutTransition } from '../transitions/SlideOutTransition'; -import { FadeTransition } from '../transitions/FadeTransition'; -import { Spinner } from '../Spinner/Spinner'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import tinycolor from 'tinycolor2'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; +import { useDelayedSwitch } from '../../utils/useDelayedSwitch'; +import { Spinner } from '../Spinner/Spinner'; +import { FadeTransition } from '../transitions/FadeTransition'; +import { SlideOutTransition } from '../transitions/SlideOutTransition'; + const getStyles = (theme: GrafanaTheme2) => { const singleValue = css` label: singleValue; diff --git a/packages/grafana-ui/src/components/Select/ValueContainer.tsx b/packages/grafana-ui/src/components/Select/ValueContainer.tsx index fbcf76805da..91fee0e95c3 100644 --- a/packages/grafana-ui/src/components/Select/ValueContainer.tsx +++ b/packages/grafana-ui/src/components/Select/ValueContainer.tsx @@ -1,7 +1,10 @@ -import React, { ReactNode } from 'react'; import { cx } from '@emotion/css'; +import React, { ReactNode } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { withTheme2 } from '../../themes/ThemeContext'; + import { getSelectStyles } from './getSelectStyles'; class UnthemedValueContainer extends React.Component { diff --git a/packages/grafana-ui/src/components/Select/getSelectStyles.ts b/packages/grafana-ui/src/components/Select/getSelectStyles.ts index e3e2c7a4606..09b2eb3ec58 100644 --- a/packages/grafana-ui/src/components/Select/getSelectStyles.ts +++ b/packages/grafana-ui/src/components/Select/getSelectStyles.ts @@ -1,7 +1,9 @@ -import { stylesFactory } from '../../themes/stylesFactory'; import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; +import { stylesFactory } from '../../themes/stylesFactory'; + export const getSelectStyles = stylesFactory((theme: GrafanaTheme2) => { return { menu: css` diff --git a/packages/grafana-ui/src/components/Select/mockOptions.tsx b/packages/grafana-ui/src/components/Select/mockOptions.tsx index 56eea3cfae0..de67501cd00 100644 --- a/packages/grafana-ui/src/components/Select/mockOptions.tsx +++ b/packages/grafana-ui/src/components/Select/mockOptions.tsx @@ -1,6 +1,7 @@ -import { SelectableValue } from '@grafana/data'; import { kebabCase } from 'lodash'; +import { SelectableValue } from '@grafana/data'; + export const generateOptions = (desc = false) => { const values = [ 'Sharilyn Markowitz', diff --git a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts index 9c90d1d701e..6ffe4fa5aff 100644 --- a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts +++ b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts @@ -1,7 +1,8 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { useMemo } from 'react'; import { CSSObjectWithLabel } from 'react-select'; +import { GrafanaTheme2 } from '@grafana/data'; + export default function resetSelectStyles(theme: GrafanaTheme2) { return { clearIndicator: () => ({}), diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index 12606c768c0..a634735b24a 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -1,7 +1,8 @@ -import { SelectableValue } from '@grafana/data'; import React from 'react'; import { ActionMeta as SelectActionMeta, GroupBase, OptionsOrGroups } from 'react-select'; +import { SelectableValue } from '@grafana/data'; + export type SelectValue = T | SelectableValue | T[] | Array>; export type ActionMeta = SelectActionMeta<{}>; export type InputActionMeta = { diff --git a/packages/grafana-ui/src/components/Select/utils.test.ts b/packages/grafana-ui/src/components/Select/utils.test.ts index e279b9418f1..2beda650e46 100644 --- a/packages/grafana-ui/src/components/Select/utils.test.ts +++ b/packages/grafana-ui/src/components/Select/utils.test.ts @@ -1,7 +1,8 @@ -import { cleanValue, findSelectedValue } from './utils'; -import { SelectableOptGroup } from './types'; import { SelectableValue } from '@grafana/data'; +import { SelectableOptGroup } from './types'; +import { cleanValue, findSelectedValue } from './utils'; + const optGroup: SelectableOptGroup[] = [ { label: 'Group 1', diff --git a/packages/grafana-ui/src/components/Select/utils.ts b/packages/grafana-ui/src/components/Select/utils.ts index a564781c398..5972ec6a888 100644 --- a/packages/grafana-ui/src/components/Select/utils.ts +++ b/packages/grafana-ui/src/components/Select/utils.ts @@ -1,4 +1,5 @@ import { SelectableValue } from '@grafana/data'; + import { SelectableOptGroup } from './types'; /** diff --git a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx index e80746a436d..7d6f2089a4e 100644 --- a/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx +++ b/packages/grafana-ui/src/components/SetInterval/SetInterval.tsx @@ -1,9 +1,10 @@ +import { isEqual } from 'lodash'; import { PureComponent } from 'react'; import { interval, Subscription, Subject, of, NEVER } from 'rxjs'; import { tap, switchMap } from 'rxjs/operators'; -import { isEqual } from 'lodash'; import { stringToMs, SelectableValue } from '@grafana/data'; + import { RefreshPicker } from '../RefreshPicker/RefreshPicker'; export function getIntervalFromString(strInterval: string): SelectableValue { diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts index b7f6778f40c..f4ca88af522 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts @@ -1,6 +1,7 @@ -import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from './SingleStatBaseOptions'; import { PanelModel } from '@grafana/data'; +import { sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from './SingleStatBaseOptions'; + describe('sharedSingleStatMigrationHandler', () => { it('from old valueOptions model without pluginVersion', () => { const panel = { diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts index c884ac1b7f9..ee5febb6c56 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts @@ -1,6 +1,5 @@ import { cloneDeep, isNumber, omit } from 'lodash'; -import { OptionsWithTextFormatting } from '@grafana/schema'; import { convertOldAngularValueMappings, FieldColorModeId, @@ -17,6 +16,7 @@ import { ValueMapping, VizOrientation, } from '@grafana/data'; +import { OptionsWithTextFormatting } from '@grafana/schema'; export interface SingleStatBaseOptions extends OptionsWithTextFormatting { reduceOptions: ReduceDataOptions; diff --git a/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx b/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx index ca4f1807380..32626f110bc 100644 --- a/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx +++ b/packages/grafana-ui/src/components/Slider/RangeSlider.story.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { RangeSlider } from '@grafana/ui'; -import { RangeSliderProps } from './types'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import { RangeSlider } from '@grafana/ui'; + +import { RangeSliderProps } from './types'; export default { title: 'Forms/Slider/Range', diff --git a/packages/grafana-ui/src/components/Slider/RangeSlider.test.tsx b/packages/grafana-ui/src/components/Slider/RangeSlider.test.tsx index 6011cbff161..081921efa8e 100644 --- a/packages/grafana-ui/src/components/Slider/RangeSlider.test.tsx +++ b/packages/grafana-ui/src/components/Slider/RangeSlider.test.tsx @@ -1,7 +1,8 @@ +import { render } from '@testing-library/react'; import React from 'react'; + import { RangeSlider } from './RangeSlider'; import { RangeSliderProps } from './types'; -import { render } from '@testing-library/react'; const sliderProps: RangeSliderProps = { min: 10, diff --git a/packages/grafana-ui/src/components/Slider/RangeSlider.tsx b/packages/grafana-ui/src/components/Slider/RangeSlider.tsx index 02fefaf5036..6fbbd592022 100644 --- a/packages/grafana-ui/src/components/Slider/RangeSlider.tsx +++ b/packages/grafana-ui/src/components/Slider/RangeSlider.tsx @@ -1,8 +1,10 @@ -import React, { FunctionComponent } from 'react'; -import { Range as RangeComponent, createSliderWithTooltip } from 'rc-slider'; import { cx } from '@emotion/css'; import { Global } from '@emotion/react'; +import { Range as RangeComponent, createSliderWithTooltip } from 'rc-slider'; +import React, { FunctionComponent } from 'react'; + import { useTheme2 } from '../../themes/ThemeContext'; + import { getStyles } from './styles'; import { RangeSliderProps } from './types'; diff --git a/packages/grafana-ui/src/components/Slider/Slider.story.tsx b/packages/grafana-ui/src/components/Slider/Slider.story.tsx index 4263897134b..453562e6a32 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.story.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.story.tsx @@ -1,8 +1,11 @@ -import React from 'react'; -import { Slider } from '@grafana/ui'; -import { SliderProps } from './types'; -import { Orientation } from '../../types/orientation'; import { Story, Meta } from '@storybook/react'; +import React from 'react'; + +import { Slider } from '@grafana/ui'; + +import { Orientation } from '../../types/orientation'; + +import { SliderProps } from './types'; export default { title: 'Forms/Slider', diff --git a/packages/grafana-ui/src/components/Slider/Slider.test.tsx b/packages/grafana-ui/src/components/Slider/Slider.test.tsx index d4274a8dc4b..a3f3e30d4d0 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.test.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.test.tsx @@ -1,7 +1,8 @@ +import { mount } from 'enzyme'; import React from 'react'; + import { Slider } from './Slider'; import { SliderProps } from './types'; -import { mount } from 'enzyme'; const sliderProps: SliderProps = { min: 10, diff --git a/packages/grafana-ui/src/components/Slider/Slider.tsx b/packages/grafana-ui/src/components/Slider/Slider.tsx index 57c7b643302..7f161850ec2 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.tsx @@ -1,12 +1,13 @@ -import React, { useState, useCallback, ChangeEvent, FunctionComponent, FocusEvent } from 'react'; -import SliderComponent from 'rc-slider'; - import { cx } from '@emotion/css'; import { Global } from '@emotion/react'; +import SliderComponent from 'rc-slider'; +import React, { useState, useCallback, ChangeEvent, FunctionComponent, FocusEvent } from 'react'; + import { useTheme2 } from '../../themes/ThemeContext'; +import { Input } from '../Input/Input'; + import { getStyles } from './styles'; import { SliderProps } from './types'; -import { Input } from '../Input/Input'; /** * @public diff --git a/packages/grafana-ui/src/components/Slider/styles.ts b/packages/grafana-ui/src/components/Slider/styles.ts index d3fb8df7b57..3e37f1a3a54 100644 --- a/packages/grafana-ui/src/components/Slider/styles.ts +++ b/packages/grafana-ui/src/components/Slider/styles.ts @@ -1,7 +1,9 @@ -import { stylesFactory } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; -import { css as cssCore } from '@emotion/react'; import { css } from '@emotion/css'; +import { css as cssCore } from '@emotion/react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory } from '../../themes'; export const getStyles = stylesFactory((theme: GrafanaTheme2, isHorizontal: boolean, hasMarks = false) => { const { spacing } = theme; diff --git a/packages/grafana-ui/src/components/Slider/types.ts b/packages/grafana-ui/src/components/Slider/types.ts index aed05a6dd03..2551ac84937 100644 --- a/packages/grafana-ui/src/components/Slider/types.ts +++ b/packages/grafana-ui/src/components/Slider/types.ts @@ -1,4 +1,5 @@ import { SliderMarks } from '@grafana/data'; + import { Orientation } from '../../types/orientation'; interface CommonSliderProps { diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index c956b401e0e..270b103aa9f 100755 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -1,5 +1,7 @@ +import { isEqual } from 'lodash'; import React, { PureComponent } from 'react'; import { AlignedData, Range } from 'uplot'; + import { compareDataFrameStructures, DataFrame, @@ -17,12 +19,13 @@ import { ScaleDirection, ScaleOrientation, } from '@grafana/schema'; -import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; -import { UPlotChart } from '../uPlot/Plot'; + import { Themeable2 } from '../../types'; +import { UPlotChart } from '../uPlot/Plot'; +import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; import { preparePlotData2, getStackingGroups } from '../uPlot/utils'; + import { preparePlotFrame } from './utils'; -import { isEqual } from 'lodash'; export interface SparklineProps extends Themeable2 { width: number; diff --git a/packages/grafana-ui/src/components/Sparkline/utils.ts b/packages/grafana-ui/src/components/Sparkline/utils.ts index 9f57db07152..0d8952f9daa 100644 --- a/packages/grafana-ui/src/components/Sparkline/utils.ts +++ b/packages/grafana-ui/src/components/Sparkline/utils.ts @@ -1,5 +1,6 @@ import { DataFrame, FieldConfig, FieldSparkline, IndexVector } from '@grafana/data'; import { GraphFieldConfig } from '@grafana/schema'; + import { applyNullInsertThreshold } from '../GraphNG/nullInsertThreshold'; /** @internal diff --git a/packages/grafana-ui/src/components/Spinner/Spinner.story.tsx b/packages/grafana-ui/src/components/Spinner/Spinner.story.tsx index efa629230a6..26fab9a2b4a 100644 --- a/packages/grafana-ui/src/components/Spinner/Spinner.story.tsx +++ b/packages/grafana-ui/src/components/Spinner/Spinner.story.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { Meta, Story } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React from 'react'; + import { Spinner } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { Props } from './Spinner'; import mdx from './Spinner.mdx'; diff --git a/packages/grafana-ui/src/components/Spinner/Spinner.tsx b/packages/grafana-ui/src/components/Spinner/Spinner.tsx index a77f80a9e5b..ad25d9fbc65 100644 --- a/packages/grafana-ui/src/components/Spinner/Spinner.tsx +++ b/packages/grafana-ui/src/components/Spinner/Spinner.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { cx, css } from '@emotion/css'; +import React, { FC } from 'react'; + import { stylesFactory } from '../../themes'; import { Icon } from '../Icon/Icon'; diff --git a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx index 1ae6bb255eb..906dec9004f 100644 --- a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx +++ b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx @@ -1,9 +1,11 @@ +import { action } from '@storybook/addon-actions'; +import { Meta, Story } from '@storybook/react'; import React, { PureComponent } from 'react'; -import { action } from '@storybook/addon-actions'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { StatsPicker } from '@grafana/ui'; -import { Meta, Story } from '@storybook/react'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { Props } from './StatsPicker'; interface State { diff --git a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx index 6742fa7c573..8fedd043c67 100644 --- a/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx +++ b/packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx @@ -1,11 +1,10 @@ +import { difference } from 'lodash'; import React, { PureComponent } from 'react'; -import { difference } from 'lodash'; +import { fieldReducers, SelectableValue } from '@grafana/data'; import { Select } from '../Select/Select'; -import { fieldReducers, SelectableValue } from '@grafana/data'; - export interface Props { placeholder?: string; onChange: (stats: string[]) => void; diff --git a/packages/grafana-ui/src/components/Switch/Switch.story.tsx b/packages/grafana-ui/src/components/Switch/Switch.story.tsx index 1210266f1ce..638c51d834c 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.story.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.story.tsx @@ -1,10 +1,13 @@ -import React, { useState, useCallback } from 'react'; import { Meta, Story } from '@storybook/react'; -import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React, { useState, useCallback } from 'react'; + import { InlineField, Switch, InlineSwitch } from '@grafana/ui'; -import mdx from './Switch.mdx'; -import { InlineFieldRow } from '../Forms/InlineFieldRow'; + +import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Field } from '../Forms/Field'; +import { InlineFieldRow } from '../Forms/InlineFieldRow'; + +import mdx from './Switch.mdx'; export default { title: 'Forms/Switch', diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index c80c8fae90c..d1d22d44d26 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -1,7 +1,9 @@ -import React, { HTMLProps, useRef } from 'react'; import { css, cx } from '@emotion/css'; import { uniqueId } from 'lodash'; +import React, { HTMLProps, useRef } from 'react'; + import { GrafanaTheme2, deprecationWarning } from '@grafana/data'; + import { stylesFactory, useTheme2 } from '../../themes'; import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins'; diff --git a/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx b/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx index 233afeffd38..f4a14cfe11e 100644 --- a/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx +++ b/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx @@ -1,9 +1,10 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; -import { stylesFactory, useTheme2 } from '../../themes'; + import { IconName, TabsBar, Tab, IconButton, CustomScrollbar, TabContent } from '../..'; +import { stylesFactory, useTheme2 } from '../../themes'; export interface TabConfig { label: string; @@ -27,7 +28,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => { tabContent: css` padding: ${theme.spacing(2)}; background-color: ${theme.colors.background.primary}; - height: 100%; + height: calc(100% - ${theme.components.menuTabs.height}px); `, close: css` position: absolute; diff --git a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx index 1d68b923893..cf63bc98f01 100644 --- a/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx +++ b/packages/grafana-ui/src/components/Table/BarGaugeCell.tsx @@ -1,9 +1,12 @@ -import React, { FC } from 'react'; -import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMinMax, LinkModel } from '@grafana/data'; -import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge'; -import { TableCellProps, TableCellDisplayMode } from './types'; -import { DataLinksContextMenu, DataLinksContextMenuApi } from '../DataLinks/DataLinksContextMenu'; import { isFunction } from 'lodash'; +import React, { FC } from 'react'; + +import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMinMax, LinkModel } from '@grafana/data'; + +import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge'; +import { DataLinksContextMenu, DataLinksContextMenuApi } from '../DataLinks/DataLinksContextMenu'; + +import { TableCellProps, TableCellDisplayMode } from './types'; const defaultScale: ThresholdsConfig = { mode: ThresholdsMode.Absolute, diff --git a/packages/grafana-ui/src/components/Table/CellActions.tsx b/packages/grafana-ui/src/components/Table/CellActions.tsx index bf140fca42d..d0b368bdf55 100644 --- a/packages/grafana-ui/src/components/Table/CellActions.tsx +++ b/packages/grafana-ui/src/components/Table/CellActions.tsx @@ -1,8 +1,10 @@ import React, { useCallback, useState } from 'react'; + import { IconSize } from '../../types/icon'; import { IconButton } from '../IconButton/IconButton'; import { HorizontalGroup } from '../Layout/Layout'; import { TooltipPlacement } from '../Tooltip'; + import { TableCellInspectModal } from './TableCellInspectModal'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps, TableFieldOptions } from './types'; import { getTextAlign } from './utils'; diff --git a/packages/grafana-ui/src/components/Table/DefaultCell.tsx b/packages/grafana-ui/src/components/Table/DefaultCell.tsx index 476cd60b492..9730270d562 100644 --- a/packages/grafana-ui/src/components/Table/DefaultCell.tsx +++ b/packages/grafana-ui/src/components/Table/DefaultCell.tsx @@ -1,11 +1,13 @@ import React, { FC, ReactElement } from 'react'; +import tinycolor from 'tinycolor2'; + import { DisplayValue, Field, formattedValueToString } from '@grafana/data'; -import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types'; -import tinycolor from 'tinycolor2'; -import { TableStyles } from './styles'; import { getTextColorForBackground, getCellLinks } from '../../utils'; + import { CellActions } from './CellActions'; +import { TableStyles } from './styles'; +import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types'; export const DefaultCell: FC = (props) => { const { field, cell, tableStyles, row, cellProps } = props; diff --git a/packages/grafana-ui/src/components/Table/Filter.tsx b/packages/grafana-ui/src/components/Table/Filter.tsx index be30f07871a..824bcdfc3b2 100644 --- a/packages/grafana-ui/src/components/Table/Filter.tsx +++ b/packages/grafana-ui/src/components/Table/Filter.tsx @@ -1,12 +1,14 @@ -import React, { FC, useCallback, useMemo, useRef, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, useCallback, useMemo, useRef, useState } from 'react'; + import { Field, GrafanaTheme } from '@grafana/data'; -import { TableStyles } from './styles'; +import { Popover } from '..'; import { stylesFactory, useStyles } from '../../themes'; import { Icon } from '../Icon/Icon'; + import { FilterPopup } from './FilterPopup'; -import { Popover } from '..'; +import { TableStyles } from './styles'; interface Props { column: any; diff --git a/packages/grafana-ui/src/components/Table/FilterList.tsx b/packages/grafana-ui/src/components/Table/FilterList.tsx index d14bd9c36d4..0d914f8f134 100644 --- a/packages/grafana-ui/src/components/Table/FilterList.tsx +++ b/packages/grafana-ui/src/components/Table/FilterList.tsx @@ -1,10 +1,11 @@ +import { css } from '@emotion/css'; import React, { FC, useCallback, useMemo, useState } from 'react'; import { FixedSizeList as List } from 'react-window'; -import { css } from '@emotion/css'; + import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { stylesFactory, useTheme2 } from '../../themes'; import { Checkbox, FilterInput, Label, VerticalGroup } from '..'; +import { stylesFactory, useTheme2 } from '../../themes'; interface Props { values: SelectableValue[]; diff --git a/packages/grafana-ui/src/components/Table/FilterPopup.tsx b/packages/grafana-ui/src/components/Table/FilterPopup.tsx index c5cebb9a1f7..4045839e718 100644 --- a/packages/grafana-ui/src/components/Table/FilterPopup.tsx +++ b/packages/grafana-ui/src/components/Table/FilterPopup.tsx @@ -1,11 +1,13 @@ -import React, { FC, useCallback, useMemo, useState } from 'react'; -import { Field, GrafanaTheme, SelectableValue } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { FC, useCallback, useMemo, useState } from 'react'; + +import { Field, GrafanaTheme, SelectableValue } from '@grafana/data'; -import { TableStyles } from './styles'; -import { stylesFactory, useStyles, useTheme2 } from '../../themes'; import { Button, ClickOutsideWrapper, HorizontalGroup, IconButton, Label, VerticalGroup } from '..'; +import { stylesFactory, useStyles, useTheme2 } from '../../themes'; + import { FilterList } from './FilterList'; +import { TableStyles } from './styles'; import { calculateUniqueFieldValues, getFilteredOptions, valuesToOptions } from './utils'; interface Props { diff --git a/packages/grafana-ui/src/components/Table/FooterCell.tsx b/packages/grafana-ui/src/components/Table/FooterCell.tsx index 98a950d637d..cadfb05ed31 100644 --- a/packages/grafana-ui/src/components/Table/FooterCell.tsx +++ b/packages/grafana-ui/src/components/Table/FooterCell.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { FooterItem } from './types'; -import { KeyValue } from '@grafana/data'; import { css } from '@emotion/css'; +import React from 'react'; + +import { KeyValue } from '@grafana/data'; + +import { FooterItem } from './types'; export interface FooterProps { value: FooterItem; diff --git a/packages/grafana-ui/src/components/Table/FooterRow.tsx b/packages/grafana-ui/src/components/Table/FooterRow.tsx index c032c3d3597..f92a393a811 100644 --- a/packages/grafana-ui/src/components/Table/FooterRow.tsx +++ b/packages/grafana-ui/src/components/Table/FooterRow.tsx @@ -1,10 +1,13 @@ import React from 'react'; import { ColumnInstance, HeaderGroup } from 'react-table'; + import { selectors } from '@grafana/e2e-selectors'; -import { getTableStyles, TableStyles } from './styles'; + import { useStyles2 } from '../../themes'; -import { FooterItem } from './types'; + import { EmptyCell, FooterCell } from './FooterCell'; +import { getTableStyles, TableStyles } from './styles'; +import { FooterItem } from './types'; export interface FooterRowProps { totalColumnsWidth: number; diff --git a/packages/grafana-ui/src/components/Table/GeoCell.tsx b/packages/grafana-ui/src/components/Table/GeoCell.tsx index be42a323deb..1ac13627427 100644 --- a/packages/grafana-ui/src/components/Table/GeoCell.tsx +++ b/packages/grafana-ui/src/components/Table/GeoCell.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { TableCellProps } from './types'; -import { Geometry } from 'ol/geom'; import WKT from 'ol/format/WKT'; +import { Geometry } from 'ol/geom'; +import React from 'react'; + +import { TableCellProps } from './types'; export function GeoCell(props: TableCellProps): JSX.Element { const { cell, tableStyles, cellProps } = props; diff --git a/packages/grafana-ui/src/components/Table/HeaderRow.tsx b/packages/grafana-ui/src/components/Table/HeaderRow.tsx index a935b961249..2c59d649eaf 100644 --- a/packages/grafana-ui/src/components/Table/HeaderRow.tsx +++ b/packages/grafana-ui/src/components/Table/HeaderRow.tsx @@ -1,12 +1,15 @@ import React from 'react'; import { HeaderGroup, Column } from 'react-table'; + import { DataFrame, Field } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { getTableStyles, TableStyles } from './styles'; + import { useStyles2 } from '../../themes'; -import { Filter } from './Filter'; -import { Icon } from '../Icon/Icon'; import { getFieldTypeIcon } from '../../types'; +import { Icon } from '../Icon/Icon'; + +import { Filter } from './Filter'; +import { getTableStyles, TableStyles } from './styles'; export interface HeaderRowProps { headerGroups: HeaderGroup[]; diff --git a/packages/grafana-ui/src/components/Table/ImageCell.tsx b/packages/grafana-ui/src/components/Table/ImageCell.tsx index 1a11d3eede7..e9d9d42b981 100644 --- a/packages/grafana-ui/src/components/Table/ImageCell.tsx +++ b/packages/grafana-ui/src/components/Table/ImageCell.tsx @@ -1,5 +1,7 @@ import React, { FC } from 'react'; + import { getCellLinks } from '../../utils'; + import { TableCellProps } from './types'; export const ImageCell: FC = (props) => { diff --git a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx index 6efeb2feb10..6f2c62a8c9a 100644 --- a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx +++ b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; import { isString } from 'lodash'; -import { TableCellProps, TableFieldOptions } from './types'; -import { CellActions } from './CellActions'; +import React from 'react'; + import { getCellLinks } from '../../utils'; +import { CellActions } from './CellActions'; +import { TableCellProps, TableFieldOptions } from './types'; + export function JSONViewCell(props: TableCellProps): JSX.Element { const { cell, tableStyles, cellProps, field, row } = props; const inspectEnabled = Boolean((field.config.custom as TableFieldOptions)?.inspect); diff --git a/packages/grafana-ui/src/components/Table/Table.story.tsx b/packages/grafana-ui/src/components/Table/Table.story.tsx index 385206a6e57..194cb581618 100644 --- a/packages/grafana-ui/src/components/Table/Table.story.tsx +++ b/packages/grafana-ui/src/components/Table/Table.story.tsx @@ -1,10 +1,7 @@ -import React from 'react'; -import { merge } from 'lodash'; -import { Table } from '@grafana/ui'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Meta, Story } from '@storybook/react'; -import { useTheme2 } from '../../themes'; -import mdx from './Table.mdx'; +import { merge } from 'lodash'; +import React from 'react'; + import { DataFrame, FieldType, @@ -15,7 +12,13 @@ import { FieldConfig, formattedValueToString, } from '@grafana/data'; +import { Table } from '@grafana/ui'; + +import { useTheme2 } from '../../themes'; import { prepDataForStorybook } from '../../utils/storybook/data'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import mdx from './Table.mdx'; import { FooterItem } from './types'; export default { diff --git a/packages/grafana-ui/src/components/Table/Table.test.tsx b/packages/grafana-ui/src/components/Table/Table.test.tsx index eeb86e323f7..1eb1e91be58 100644 --- a/packages/grafana-ui/src/components/Table/Table.test.tsx +++ b/packages/grafana-ui/src/components/Table/Table.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { applyFieldOverrides, createTheme, DataFrame, FieldType, toDataFrame } from '@grafana/data'; + import { Props, Table } from './Table'; function getDefaultDataFrame(): DataFrame { @@ -159,11 +161,11 @@ describe('Table', () => { }); describe('when sorting with column header', () => { - it('then correct rows should be rendered', () => { + it('then correct rows should be rendered', async () => { getTestContext(); - userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i)); - userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i)); + await userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i)); + await userEvent.click(within(getColumnHeader(/temperature/)).getByText(/temperature/i)); const rows = within(getTable()).getAllByRole('row'); expect(rows).toHaveLength(5); diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 1097fad630f..7e407d12e04 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -1,5 +1,4 @@ import React, { FC, memo, useCallback, useEffect, useMemo } from 'react'; -import { DataFrame, getFieldDisplayName } from '@grafana/data'; import { Cell, Column, @@ -12,7 +11,17 @@ import { useTable, } from 'react-table'; import { FixedSizeList } from 'react-window'; -import { getColumns, sortCaseInsensitive, sortNumber } from './utils'; + +import { DataFrame, getFieldDisplayName } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; +import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; +import { Pagination } from '../Pagination/Pagination'; + +import { FooterRow } from './FooterRow'; +import { HeaderRow } from './HeaderRow'; +import { TableCell } from './TableCell'; +import { getTableStyles } from './styles'; import { TableColumnResizeActionCallback, TableFilterActionCallback, @@ -20,13 +29,7 @@ import { TableSortByActionCallback, TableSortByFieldState, } from './types'; -import { getTableStyles } from './styles'; -import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; -import { TableCell } from './TableCell'; -import { useStyles2 } from '../../themes'; -import { FooterRow } from './FooterRow'; -import { HeaderRow } from './HeaderRow'; -import { Pagination } from '../Pagination/Pagination'; +import { getColumns, sortCaseInsensitive, sortNumber } from './utils'; const COLUMN_MIN_WIDTH = 150; diff --git a/packages/grafana-ui/src/components/Table/TableCell.tsx b/packages/grafana-ui/src/components/Table/TableCell.tsx index f8a075f8eb3..09b6f4bb6d5 100644 --- a/packages/grafana-ui/src/components/Table/TableCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableCell.tsx @@ -1,7 +1,8 @@ import React, { FC } from 'react'; import { Cell } from 'react-table'; -import { GrafanaTableColumn, TableFilterActionCallback } from './types'; + import { TableStyles } from './styles'; +import { GrafanaTableColumn, TableFilterActionCallback } from './types'; export interface Props { cell: Cell; diff --git a/packages/grafana-ui/src/components/Table/TableCellInspectModal.tsx b/packages/grafana-ui/src/components/Table/TableCellInspectModal.tsx index a5d204fe058..2feb934a8c0 100644 --- a/packages/grafana-ui/src/components/Table/TableCellInspectModal.tsx +++ b/packages/grafana-ui/src/components/Table/TableCellInspectModal.tsx @@ -1,5 +1,6 @@ import { isString } from 'lodash'; import React, { useEffect, useState } from 'react'; + import { ClipboardButton } from '../ClipboardButton/ClipboardButton'; import { Icon } from '../Icon/Icon'; import { Modal } from '../Modal/Modal'; diff --git a/packages/grafana-ui/src/components/Table/styles.ts b/packages/grafana-ui/src/components/Table/styles.ts index 3682b5da17e..7bb9d5b5b21 100644 --- a/packages/grafana-ui/src/components/Table/styles.ts +++ b/packages/grafana-ui/src/components/Table/styles.ts @@ -1,5 +1,7 @@ import { css, CSSObject } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; + import { getScrollbarWidth } from '../../utils'; export const getTableStyles = (theme: GrafanaTheme2) => { diff --git a/packages/grafana-ui/src/components/Table/types.ts b/packages/grafana-ui/src/components/Table/types.ts index aa7480bb35e..82441536f51 100644 --- a/packages/grafana-ui/src/components/Table/types.ts +++ b/packages/grafana-ui/src/components/Table/types.ts @@ -1,8 +1,10 @@ -import { CellProps, Column, Row } from 'react-table'; -import { Field, KeyValue, SelectableValue } from '@grafana/data'; -import { TableStyles } from './styles'; -import { FC } from 'react'; import { Property } from 'csstype'; +import { FC } from 'react'; +import { CellProps, Column, Row } from 'react-table'; + +import { Field, KeyValue, SelectableValue } from '@grafana/data'; + +import { TableStyles } from './styles'; export { TableFieldOptions, TableCellDisplayMode, FieldTextAlignment } from '@grafana/schema'; diff --git a/packages/grafana-ui/src/components/Table/utils.test.ts b/packages/grafana-ui/src/components/Table/utils.test.ts index bb2ff5b70d9..1dad733f3ca 100644 --- a/packages/grafana-ui/src/components/Table/utils.test.ts +++ b/packages/grafana-ui/src/components/Table/utils.test.ts @@ -1,4 +1,5 @@ import { ArrayVector, Field, FieldType, MutableDataFrame, SelectableValue } from '@grafana/data'; + import { calculateUniqueFieldValues, filterByValue, diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts index 6691efef841..5b6e13c740f 100644 --- a/packages/grafana-ui/src/components/Table/utils.ts +++ b/packages/grafana-ui/src/components/Table/utils.ts @@ -1,6 +1,7 @@ -import { Row } from 'react-table'; -import memoizeOne from 'memoize-one'; import { Property } from 'csstype'; +import memoizeOne from 'memoize-one'; +import { Row } from 'react-table'; + import { DataFrame, Field, @@ -10,13 +11,13 @@ import { SelectableValue, } from '@grafana/data'; -import { DefaultCell } from './DefaultCell'; import { BarGaugeCell } from './BarGaugeCell'; -import { CellComponent, TableCellDisplayMode, TableFieldOptions, FooterItem, GrafanaTableColumn } from './types'; -import { JSONViewCell } from './JSONViewCell'; +import { DefaultCell } from './DefaultCell'; +import { getFooterValue } from './FooterRow'; import { GeoCell } from './GeoCell'; import { ImageCell } from './ImageCell'; -import { getFooterValue } from './FooterRow'; +import { JSONViewCell } from './JSONViewCell'; +import { CellComponent, TableCellDisplayMode, TableFieldOptions, FooterItem, GrafanaTableColumn } from './types'; export function getTextAlign(field?: Field): Property.JustifyContent { if (!field) { diff --git a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.internal.tsx b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.internal.tsx index b3ad26c955a..f7aa4f7cb34 100644 --- a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.internal.tsx +++ b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.story.internal.tsx @@ -1,11 +1,13 @@ +import { action } from '@storybook/addon-actions'; +import { Meta } from '@storybook/react'; import React from 'react'; -import { TableInputCSV } from './TableInputCSV'; -import { Meta } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; import { DataFrame } from '@grafana/data'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { TableInputCSV } from './TableInputCSV'; + export default { title: 'Forms/TableInputCSV', component: TableInputCSV, diff --git a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.test.tsx b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.test.tsx index e2953fc1c43..246352eff1b 100644 --- a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.test.tsx +++ b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.test.tsx @@ -1,9 +1,10 @@ import React from 'react'; - import renderer from 'react-test-renderer'; -import { TableInputCSV } from './TableInputCSV'; + import { DataFrame } from '@grafana/data'; +import { TableInputCSV } from './TableInputCSV'; + describe('TableInputCSV', () => { it('renders correctly', () => { const tree = renderer diff --git a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx index 0a9deb6bc63..70665de1fd7 100644 --- a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx +++ b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx @@ -1,11 +1,13 @@ -import React from 'react'; -import { debounce } from 'lodash'; import { css } from '@emotion/css'; +import { debounce } from 'lodash'; +import React from 'react'; + import { GrafanaTheme, DataFrame, CSVConfig, readCSV } from '@grafana/data'; -import { Icon } from '../Icon/Icon'; -import { Themeable } from '../../types/theme'; -import { TextArea } from '../TextArea/TextArea'; + import { stylesFactory, withTheme } from '../../themes'; +import { Themeable } from '../../types/theme'; +import { Icon } from '../Icon/Icon'; +import { TextArea } from '../TextArea/TextArea'; interface Props extends Themeable { config?: CSVConfig; diff --git a/packages/grafana-ui/src/components/Tabs/Counter.tsx b/packages/grafana-ui/src/components/Tabs/Counter.tsx index e90e621787b..7bb63b2ecd9 100644 --- a/packages/grafana-ui/src/components/Tabs/Counter.tsx +++ b/packages/grafana-ui/src/components/Tabs/Counter.tsx @@ -1,8 +1,10 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; -import { stylesFactory, useStyles2 } from '../../themes'; +import React, { FC } from 'react'; + import { GrafanaTheme2, locale } from '@grafana/data'; +import { stylesFactory, useStyles2 } from '../../themes'; + const getStyles = stylesFactory((theme: GrafanaTheme2) => { return { counter: css` diff --git a/packages/grafana-ui/src/components/Tabs/Tab.tsx b/packages/grafana-ui/src/components/Tabs/Tab.tsx index d45f33cc436..ffa743b861c 100644 --- a/packages/grafana-ui/src/components/Tabs/Tab.tsx +++ b/packages/grafana-ui/src/components/Tabs/Tab.tsx @@ -1,13 +1,15 @@ -import React, { HTMLProps } from 'react'; import { css, cx } from '@emotion/css'; +import React, { HTMLProps } from 'react'; + import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Icon } from '../Icon/Icon'; -import { IconName } from '../../types'; import { stylesFactory, useTheme2 } from '../../themes'; -import { Counter } from './Counter'; import { getFocusStyles } from '../../themes/mixins'; +import { IconName } from '../../types'; +import { Icon } from '../Icon/Icon'; + +import { Counter } from './Counter'; export interface TabProps extends HTMLProps { label: string; diff --git a/packages/grafana-ui/src/components/Tabs/TabContent.tsx b/packages/grafana-ui/src/components/Tabs/TabContent.tsx index 6168f0f6767..eb50ed3a974 100644 --- a/packages/grafana-ui/src/components/Tabs/TabContent.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabContent.tsx @@ -1,8 +1,10 @@ -import React, { FC, HTMLAttributes, ReactNode } from 'react'; -import { stylesFactory, useTheme2 } from '../../themes'; import { css, cx } from '@emotion/css'; +import React, { FC, HTMLAttributes, ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { stylesFactory, useTheme2 } from '../../themes'; + interface Props extends HTMLAttributes { children: ReactNode; } diff --git a/packages/grafana-ui/src/components/Tabs/Tabs.story.tsx b/packages/grafana-ui/src/components/Tabs/Tabs.story.tsx index bedfe9cdea8..43adec962f4 100644 --- a/packages/grafana-ui/src/components/Tabs/Tabs.story.tsx +++ b/packages/grafana-ui/src/components/Tabs/Tabs.story.tsx @@ -1,10 +1,13 @@ -import React, { useState } from 'react'; import { Story } from '@storybook/react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React, { useState } from 'react'; + import { TabsBar, Tab, TabContent, Counter as TabCounter } from '@grafana/ui'; -import mdx from './TabsBar.mdx'; -import { CounterProps } from './Counter'; + import { DashboardStoryCanvas } from '../../utils/storybook/DashboardStoryCanvas'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { CounterProps } from './Counter'; +import mdx from './TabsBar.mdx'; export default { title: 'Layout/Tabs', diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx index 13fccb9223f..6cfb03409be 100644 --- a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx @@ -1,7 +1,9 @@ -import React, { ReactNode } from 'react'; -import { stylesFactory, useTheme2 } from '../../themes'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { ReactNode } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { stylesFactory, useTheme2 } from '../../themes'; export interface Props { /** Children should be a single or an array of */ @@ -21,7 +23,7 @@ const getTabsBarStyles = stylesFactory((theme: GrafanaTheme2, hideBorder = false tabs: css` position: relative; display: flex; - height: 41px; + height: ${theme.components.menuTabs.height}px; `, }; }); diff --git a/packages/grafana-ui/src/components/Tags/Tag.story.tsx b/packages/grafana-ui/src/components/Tags/Tag.story.tsx index 8c67194cd82..07222066547 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.story.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.story.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; + import { Tag } from '@grafana/ui'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './Tag.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Tags/Tag.tsx b/packages/grafana-ui/src/components/Tags/Tag.tsx index 01fc15e68cc..eee65fa0cd8 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.tsx @@ -1,9 +1,11 @@ -import React, { forwardRef, HTMLAttributes } from 'react'; import { cx, css } from '@emotion/css'; +import React, { forwardRef, HTMLAttributes } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + import { useTheme } from '../../themes'; -import { getTagColor, getTagColorsFromName } from '../../utils'; import { IconName } from '../../types/icon'; +import { getTagColor, getTagColorsFromName } from '../../utils'; import { Icon } from '../Icon/Icon'; /** diff --git a/packages/grafana-ui/src/components/Tags/TagList.story.tsx b/packages/grafana-ui/src/components/Tags/TagList.story.tsx index 0f42221e34f..f4281880aef 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.story.tsx +++ b/packages/grafana-ui/src/components/Tags/TagList.story.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; -import { TagList } from './TagList'; +import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { TagList } from './TagList'; import mdx from './TagList.mdx'; export default { diff --git a/packages/grafana-ui/src/components/Tags/TagList.tsx b/packages/grafana-ui/src/components/Tags/TagList.tsx index 25e6825f1f6..d22be614c5a 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.tsx +++ b/packages/grafana-ui/src/components/Tags/TagList.tsx @@ -1,10 +1,13 @@ -import React, { forwardRef, memo } from 'react'; import { css, cx } from '@emotion/css'; -import { OnTagClick, Tag } from './Tag'; -import { useTheme2 } from '../../themes'; +import React, { forwardRef, memo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; + +import { useTheme2 } from '../../themes'; import { IconName } from '../../types/icon'; +import { OnTagClick, Tag } from './Tag'; + export interface Props { displayMax?: number; tags: string[]; diff --git a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx index 7af715e5f00..6ef390c1b04 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagItem.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagItem.tsx @@ -1,8 +1,10 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; -import { getTagColorsFromName } from '../../utils'; -import { stylesFactory, useTheme } from '../../themes'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + +import { stylesFactory, useTheme } from '../../themes'; +import { getTagColorsFromName } from '../../utils'; import { IconButton } from '../IconButton/IconButton'; interface Props { diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx index 74b12e197d6..d7ef294c2e4 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx @@ -1,9 +1,11 @@ -import React, { useState } from 'react'; import { Meta, Story } from '@storybook/react'; -import { TagsInput, Props } from './TagsInput'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import React, { useState } from 'react'; + import { StoryExample } from '../../utils/storybook/StoryExample'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { VerticalGroup } from '../Layout/Layout'; + +import { TagsInput, Props } from './TagsInput'; import mdx from './TagsInput.mdx'; export default { diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx index 15e8697c1fd..4c0db8c1014 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; +import React from 'react'; + import { TagsInput } from './TagsInput'; describe('TagsInput', () => { diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index bcd41ca683e..5e1246e09a3 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -1,11 +1,14 @@ -import React, { ChangeEvent, KeyboardEvent, FC, useState } from 'react'; import { css, cx } from '@emotion/css'; -import { Button } from '../Button'; -import { TagItem } from './TagItem'; -import { useStyles, useTheme2 } from '../../themes/ThemeContext'; +import React, { ChangeEvent, KeyboardEvent, FC, useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; + +import { useStyles, useTheme2 } from '../../themes/ThemeContext'; +import { Button } from '../Button'; import { Input } from '../Input/Input'; +import { TagItem } from './TagItem'; + export interface Props { placeholder?: string; /** Array of selected tags */ diff --git a/packages/grafana-ui/src/components/TextArea/TextArea.story.tsx b/packages/grafana-ui/src/components/TextArea/TextArea.story.tsx index 3508c9116b9..d2d533f3b71 100644 --- a/packages/grafana-ui/src/components/TextArea/TextArea.story.tsx +++ b/packages/grafana-ui/src/components/TextArea/TextArea.story.tsx @@ -1,7 +1,10 @@ -import React from 'react'; -import { TextArea } from '@grafana/ui'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Meta, Story } from '@storybook/react'; +import React from 'react'; + +import { TextArea } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './TextArea.mdx'; export default { diff --git a/packages/grafana-ui/src/components/TextArea/TextArea.tsx b/packages/grafana-ui/src/components/TextArea/TextArea.tsx index 679890c3245..aa235615756 100644 --- a/packages/grafana-ui/src/components/TextArea/TextArea.tsx +++ b/packages/grafana-ui/src/components/TextArea/TextArea.tsx @@ -1,6 +1,8 @@ -import React, { HTMLProps } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { HTMLProps } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { stylesFactory, useTheme2 } from '../../themes'; import { getFocusStyle, sharedInputStyle } from '../Forms/commonStyles'; diff --git a/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx b/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx index 13d8b10ccea..4d551791f88 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx @@ -1,12 +1,14 @@ /** @jsxImportSource @emotion/react */ -import { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import classnames from 'classnames'; +import { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + import { useStyles2, useTheme2 } from '../../themes'; import { Button } from '../Button'; import { VerticalGroup } from '../Layout/Layout'; -import classnames from 'classnames'; export function EmotionPerfTest() { console.log('process.env.NODE_ENV', process.env.NODE_ENV); diff --git a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx index e2d778ba0af..6b9b5a4088b 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { EmotionPerfTest } from './EmotionPerfTest'; import { ThemeDemo as NewThemeDemoComponent } from './ThemeDemo'; diff --git a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx index 60ba274bd57..f71acfc76c7 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx @@ -1,19 +1,21 @@ -import React, { FC, useState } from 'react'; import { css, cx } from '@emotion/css'; -import { useTheme2 } from '../../themes/ThemeContext'; -import { Icon } from '../Icon/Icon'; -import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; +import React, { FC, useState } from 'react'; + import { GrafanaTheme2, ThemeRichColor } from '@grafana/data'; + +import { useTheme2 } from '../../themes/ThemeContext'; +import { allButtonVariants, Button } from '../Button'; +import { Card } from '../Card/Card'; import { CollapsableSection } from '../Collapse/CollapsableSection'; import { Field } from '../Forms/Field'; -import { Input } from '../Input/Input'; -import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup'; -import { Switch } from '../Switch/Switch'; -import { allButtonVariants, Button } from '../Button'; import { InlineField } from '../Forms/InlineField'; import { InlineFieldRow } from '../Forms/InlineFieldRow'; -import { Card } from '../Card/Card'; +import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup'; +import { Icon } from '../Icon/Icon'; +import { Input } from '../Input/Input'; +import { HorizontalGroup, VerticalGroup } from '../Layout/Layout'; import { Select } from '../Select/Select'; +import { Switch } from '../Switch/Switch'; interface DemoBoxProps { bg?: string; diff --git a/packages/grafana-ui/src/components/TimeSeries/TimeSeries.tsx b/packages/grafana-ui/src/components/TimeSeries/TimeSeries.tsx index e3d54e93ce5..39ee24a3bbb 100644 --- a/packages/grafana-ui/src/components/TimeSeries/TimeSeries.tsx +++ b/packages/grafana-ui/src/components/TimeSeries/TimeSeries.tsx @@ -1,13 +1,16 @@ import React from 'react'; + import { DataFrame, TimeRange } from '@grafana/data'; -import { GraphNG, GraphNGProps } from '../GraphNG/GraphNG'; -import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; -import { PlotLegend } from '../uPlot/PlotLegend'; import { LegendDisplayMode } from '@grafana/schema'; -import { preparePlotConfigBuilder } from './utils'; -import { withTheme2 } from '../../themes/ThemeContext'; -import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext'; + import { PropDiffFn } from '../../../../../packages/grafana-ui/src/components/GraphNG/GraphNG'; +import { withTheme2 } from '../../themes/ThemeContext'; +import { GraphNG, GraphNGProps } from '../GraphNG/GraphNG'; +import { PanelContext, PanelContextRoot } from '../PanelChrome/PanelContext'; +import { PlotLegend } from '../uPlot/PlotLegend'; +import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; + +import { preparePlotConfigBuilder } from './utils'; const propsToDiff: Array = ['legend', 'options']; diff --git a/packages/grafana-ui/src/components/TimeSeries/utils.ts b/packages/grafana-ui/src/components/TimeSeries/utils.ts index ed9e084759e..062378b5124 100644 --- a/packages/grafana-ui/src/components/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/components/TimeSeries/utils.ts @@ -1,4 +1,6 @@ import { isNumber } from 'lodash'; +import uPlot from 'uplot'; + import { DashboardCursorSync, DataFrame, @@ -13,8 +15,6 @@ import { getFieldDisplayName, getDisplayProcessor, } from '@grafana/data'; - -import { UPlotConfigBuilder, UPlotConfigPrepFn } from '../uPlot/config/UPlotConfigBuilder'; import { AxisPlacement, GraphDrawStyle, @@ -25,9 +25,10 @@ import { ScaleOrientation, StackingMode, } from '@grafana/schema'; -import { getStackingGroups, preparePlotData2 } from '../uPlot/utils'; -import uPlot from 'uplot'; + import { buildScaleKey } from '../GraphNG/utils'; +import { UPlotConfigBuilder, UPlotConfigPrepFn } from '../uPlot/config/UPlotConfigBuilder'; +import { getStackingGroups, preparePlotData2 } from '../uPlot/utils'; const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1)); diff --git a/packages/grafana-ui/src/components/Tooltip/Popover.tsx b/packages/grafana-ui/src/components/Tooltip/Popover.tsx index b9197a90baa..e0b2939eb87 100644 --- a/packages/grafana-ui/src/components/Tooltip/Popover.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Popover.tsx @@ -1,8 +1,10 @@ -import React, { PureComponent } from 'react'; -import { Placement, VirtualElement } from '@popperjs/core'; +import { Placement, VirtualElement } from '@popperjs/core'; +import React, { PureComponent } from 'react'; import { Manager, Popper as ReactPopper, PopperArrowProps } from 'react-popper'; -import { Portal } from '../Portal/Portal'; import Transition from 'react-transition-group/Transition'; + +import { Portal } from '../Portal/Portal'; + import { PopoverContent } from './types'; const defaultTransitionStyles = { diff --git a/packages/grafana-ui/src/components/Tooltip/PopoverController.tsx b/packages/grafana-ui/src/components/Tooltip/PopoverController.tsx index c68cac0271d..ae5534cb65d 100644 --- a/packages/grafana-ui/src/components/Tooltip/PopoverController.tsx +++ b/packages/grafana-ui/src/components/Tooltip/PopoverController.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { Placement } from '@popperjs/core'; +import React from 'react'; + import { PopoverContent } from './types'; type PopperControllerRenderProp = ( diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.story.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.story.tsx index a3ab4d1f1f2..ee48d28c088 100644 --- a/packages/grafana-ui/src/components/Tooltip/Tooltip.story.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Tooltip.story.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { Story } from '@storybook/react'; -import { Tooltip } from './Tooltip'; +import React from 'react'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Button } from '../Button'; import mdx from '../Tooltip/Tooltip.mdx'; +import { Tooltip } from './Tooltip'; + export default { title: 'Overlays/Tooltip', component: Tooltip, diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx index d318be466e6..68dbc689ce0 100644 --- a/packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { Tooltip } from './Tooltip'; describe('Tooltip', () => { diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx index ba5cd2b2b9f..08647671b66 100644 --- a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx @@ -1,11 +1,14 @@ +import { css } from '@emotion/css'; import React from 'react'; import { usePopperTooltip } from 'react-popper-tooltip'; + import { colorManipulator, GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; + import { useStyles2 } from '../../themes/ThemeContext'; -import { PopoverContent, TooltipPlacement } from './types'; import { Portal } from '../Portal/Portal'; +import { PopoverContent, TooltipPlacement } from './types'; + export interface TooltipProps { theme?: 'info' | 'error' | 'info-alt'; show?: boolean; diff --git a/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.test.tsx b/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.test.tsx index 55008a6cf46..a73a9c63d16 100644 --- a/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.test.tsx +++ b/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; + import { PartialHighlighter } from './PartialHighlighter'; function assertPart(component: ReactWrapper, isHighlighted: boolean, text: string): void { diff --git a/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.tsx b/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.tsx index 04e967b3b7e..13bbde407e5 100644 --- a/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.tsx +++ b/packages/grafana-ui/src/components/Typeahead/PartialHighlighter.tsx @@ -1,4 +1,5 @@ import React, { createElement } from 'react'; + import { HighlightPart } from '../../types'; interface Props { diff --git a/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx b/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx index ffe7380e941..a2ac0365890 100644 --- a/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx +++ b/packages/grafana-ui/src/components/Typeahead/Typeahead.test.tsx @@ -1,9 +1,10 @@ -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; + +import { CompletionItemGroup, CompletionItemKind } from '../../types'; import { Typeahead, State } from './Typeahead'; import { TypeaheadItem } from './TypeaheadItem'; -import { CompletionItemGroup, CompletionItemKind } from '../../types'; describe('Typeahead', () => { const completionItemGroups = [{ label: 'my group', items: [{ label: 'first item' }] }]; diff --git a/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx b/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx index 3dc06442c4c..01526c98439 100644 --- a/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx +++ b/packages/grafana-ui/src/components/Typeahead/Typeahead.tsx @@ -1,13 +1,14 @@ +import { isEqual } from 'lodash'; import React, { createRef } from 'react'; import ReactDOM from 'react-dom'; -import { isEqual } from 'lodash'; import { FixedSizeList } from 'react-window'; +import { ThemeContext } from '../../themes/ThemeContext'; +import { CompletionItem, CompletionItemGroup, CompletionItemKind } from '../../types/completion'; +import { flattenGroupItems, calculateLongestLabel, calculateListSizes } from '../../utils/typeahead'; + import { TypeaheadInfo } from './TypeaheadInfo'; import { TypeaheadItem } from './TypeaheadItem'; -import { flattenGroupItems, calculateLongestLabel, calculateListSizes } from '../../utils/typeahead'; -import { ThemeContext } from '../../themes/ThemeContext'; -import { CompletionItem, CompletionItemGroup, CompletionItemKind } from '../../types/completion'; const modulo = (a: number, n: number) => a - n * Math.floor(a / n); diff --git a/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.test.tsx b/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.test.tsx index 0864fa79b84..78624ee43ef 100644 --- a/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.test.tsx +++ b/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { TypeaheadInfo } from './TypeaheadInfo'; +import React from 'react'; + import { CompletionItem } from '../../types'; +import { TypeaheadInfo } from './TypeaheadInfo'; + describe('TypeaheadInfo component', () => { it('should show documentation as rendered markdown if passed as a markdown', () => { const item: CompletionItem = { label: 'markdown', documentation: '**bold**' }; diff --git a/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx b/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx index df7b0ba3d55..30c4b10fd2a 100644 --- a/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx +++ b/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; import { GrafanaTheme, renderMarkdown } from '@grafana/data'; + import { useTheme } from '../../themes/ThemeContext'; import { CompletionItem } from '../../types'; diff --git a/packages/grafana-ui/src/components/Typeahead/TypeaheadItem.tsx b/packages/grafana-ui/src/components/Typeahead/TypeaheadItem.tsx index c2cde3dbd39..d75685d866e 100644 --- a/packages/grafana-ui/src/components/Typeahead/TypeaheadItem.tsx +++ b/packages/grafana-ui/src/components/Typeahead/TypeaheadItem.tsx @@ -1,12 +1,14 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; - // @ts-ignore import Highlighter from 'react-highlight-words'; -import { css, cx } from '@emotion/css'; + import { GrafanaTheme } from '@grafana/data'; -import { CompletionItem, CompletionItemKind } from '../../types/completion'; -import { PartialHighlighter } from './PartialHighlighter'; + import { useStyles } from '../../themes/ThemeContext'; +import { CompletionItem, CompletionItemKind } from '../../types/completion'; + +import { PartialHighlighter } from './PartialHighlighter'; interface Props { isSelected: boolean; diff --git a/packages/grafana-ui/src/components/Typography/Typography.story.tsx b/packages/grafana-ui/src/components/Typography/Typography.story.tsx index 4069fc442a1..941a2f5d2b4 100644 --- a/packages/grafana-ui/src/components/Typography/Typography.story.tsx +++ b/packages/grafana-ui/src/components/Typography/Typography.story.tsx @@ -1,6 +1,8 @@ import React from 'react'; + import { StoryExample } from '../../utils/storybook/StoryExample'; import { VerticalGroup } from '../Layout/Layout'; + import { Typography } from './Typography'; export default { diff --git a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx index 29f173341c6..558a7c5f080 100644 --- a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx +++ b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.story.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { UnitPicker } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import mdx from './UnitPicker.mdx'; export default { diff --git a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx index bbf1b48a9b8..570b4e9de5f 100644 --- a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx +++ b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx @@ -1,7 +1,9 @@ import React, { PureComponent } from 'react'; -import { Cascader, CascaderOption } from '../Cascader/Cascader'; + import { getValueFormats, SelectableValue } from '@grafana/data'; +import { Cascader, CascaderOption } from '../Cascader/Cascader'; + interface Props { onChange: (item?: string) => void; value?: string; diff --git a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.story.tsx b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.story.tsx index 96401c4edb7..920adc40273 100644 --- a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.story.tsx +++ b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.story.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { Story } from '@storybook/react'; +import React from 'react'; + import { ValuePicker } from '@grafana/ui'; -import { generateOptions } from '../Select/mockOptions'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { getAvailableIcons } from '../../types'; -import mdx from './ValuePicker.mdx'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { generateOptions } from '../Select/mockOptions'; + import { ValuePickerProps } from './ValuePicker'; +import mdx from './ValuePicker.mdx'; export default { title: 'Pickers and Editors/ValuePicker', diff --git a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx index 53c68bcb47f..9edba661e15 100644 --- a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx +++ b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx @@ -1,11 +1,13 @@ import React, { useState } from 'react'; -import { IconName } from '../../types'; + import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; + +import { useTheme2 } from '../../themes'; +import { IconName } from '../../types'; +import { ComponentSize } from '../../types/size'; import { Button, ButtonVariant } from '../Button'; import { Select } from '../Select/Select'; -import { ComponentSize } from '../../types/size'; -import { selectors } from '@grafana/e2e-selectors'; -import { useTheme2 } from '../../themes'; export interface ValuePickerProps { /** Label to display on the picker button */ diff --git a/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx b/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx index b42a145b634..c919cc70aa5 100644 --- a/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx +++ b/packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx @@ -1,6 +1,8 @@ -import React, { useEffect, useState } from 'react'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { Story } from '@storybook/react'; +import React, { useEffect, useState } from 'react'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + import { VizLayout } from './VizLayout'; export default { diff --git a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx index 83444158386..3f91557fc78 100644 --- a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx +++ b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx @@ -1,11 +1,13 @@ +import { css } from '@emotion/css'; import React, { FC, CSSProperties, ComponentType } from 'react'; import { useMeasure } from 'react-use'; -import { css } from '@emotion/css'; -import { LegendPlacement } from '@grafana/schema'; + import { GrafanaTheme2 } from '@grafana/data'; -import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; -import { getFocusStyles } from '../../themes/mixins'; +import { LegendPlacement } from '@grafana/schema'; + import { useStyles2 } from '../../themes/ThemeContext'; +import { getFocusStyles } from '../../themes/mixins'; +import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; /** * @beta diff --git a/packages/grafana-ui/src/components/VizLegend/SeriesIcon.test.tsx b/packages/grafana-ui/src/components/VizLegend/SeriesIcon.test.tsx index 037eb0472a8..79672cd68ee 100644 --- a/packages/grafana-ui/src/components/VizLegend/SeriesIcon.test.tsx +++ b/packages/grafana-ui/src/components/VizLegend/SeriesIcon.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render } from '@testing-library/react'; +import React from 'react'; + import { SeriesIcon } from './SeriesIcon'; describe('SeriesIcon', () => { diff --git a/packages/grafana-ui/src/components/VizLegend/SeriesIcon.tsx b/packages/grafana-ui/src/components/VizLegend/SeriesIcon.tsx index 89711d53307..ec91ac063ff 100644 --- a/packages/grafana-ui/src/components/VizLegend/SeriesIcon.tsx +++ b/packages/grafana-ui/src/components/VizLegend/SeriesIcon.tsx @@ -1,7 +1,9 @@ import React, { CSSProperties } from 'react'; -import { useTheme2 } from '../../themes'; + import { fieldColorModeRegistry } from '@grafana/data'; +import { useTheme2 } from '../../themes'; + export interface Props extends React.HTMLAttributes { color?: string; gradient?: string; diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx index b9bc1a66037..d5db02e57a5 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegend.story.tsx @@ -1,11 +1,13 @@ -import React, { FC, useEffect, useState } from 'react'; -import { useTheme, VizLegend } from '@grafana/ui'; import { Story, Meta } from '@storybook/react'; -import {} from './VizLegendListItem'; +import React, { FC, useEffect, useState } from 'react'; + import { DisplayValue, getColorForTheme, GrafanaTheme } from '@grafana/data'; -import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { VizLegendItem } from './types'; import { LegendDisplayMode, LegendPlacement } from '@grafana/schema'; +import { useTheme, VizLegend } from '@grafana/ui'; + +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +import { VizLegendItem } from './types'; export default { title: 'Visualizations/VizLegend', diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx index c44a72924d2..ddbc3dd383c 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx @@ -1,10 +1,13 @@ import React, { useCallback } from 'react'; -import { LegendProps, SeriesVisibilityChangeBehavior, VizLegendItem } from './types'; -import { LegendDisplayMode } from '@grafana/schema'; -import { VizLegendTable } from './VizLegendTable'; -import { VizLegendList } from './VizLegendList'; + import { DataHoverClearEvent, DataHoverEvent } from '@grafana/data'; +import { LegendDisplayMode } from '@grafana/schema'; + import { SeriesVisibilityChangeMode, usePanelContext } from '../PanelChrome'; + +import { VizLegendList } from './VizLegendList'; +import { VizLegendTable } from './VizLegendTable'; +import { LegendProps, SeriesVisibilityChangeBehavior, VizLegendItem } from './types'; import { mapMouseEventToMode } from './utils'; /** diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx index a701431caa7..d0ca8d96597 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendList.tsx @@ -1,11 +1,14 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; -import { VizLegendBaseProps, VizLegendItem } from './types'; + +import { GrafanaTheme } from '@grafana/data'; + +import { useStyles } from '../../themes'; import { InlineList } from '../List/InlineList'; import { List } from '../List/List'; -import { css, cx } from '@emotion/css'; -import { useStyles } from '../../themes'; -import { GrafanaTheme } from '@grafana/data'; + import { VizLegendListItem } from './VizLegendListItem'; +import { VizLegendBaseProps, VizLegendItem } from './types'; export interface Props extends VizLegendBaseProps {} diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 16f77707c99..1861e99fe83 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -1,12 +1,15 @@ -import React, { useCallback } from 'react'; import { css, cx } from '@emotion/css'; -import { VizLegendSeriesIcon } from './VizLegendSeriesIcon'; -import { VizLegendItem } from './types'; -import { VizLegendStatsList } from './VizLegendStatsList'; -import { useStyles } from '../../themes'; +import React, { useCallback } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { useStyles } from '../../themes'; + +import { VizLegendSeriesIcon } from './VizLegendSeriesIcon'; +import { VizLegendStatsList } from './VizLegendStatsList'; +import { VizLegendItem } from './types'; + export interface Props { item: VizLegendItem; className?: string; diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendSeriesIcon.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendSeriesIcon.tsx index 943eb6d1ac4..250de0703e7 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendSeriesIcon.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendSeriesIcon.tsx @@ -1,6 +1,8 @@ import React, { useCallback } from 'react'; + import { SeriesColorPicker } from '../ColorPicker/ColorPicker'; import { usePanelContext } from '../PanelChrome'; + import { SeriesIcon } from './SeriesIcon'; interface Props { diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx index 2fdf0db9961..c4f32f678f3 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendStatsList.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { InlineList } from '../List/InlineList'; import { css } from '@emotion/css'; -import { DisplayValue, formattedValueToString } from '@grafana/data'; import { capitalize } from 'lodash'; +import React from 'react'; + +import { DisplayValue, formattedValueToString } from '@grafana/data'; + import { useStyles } from '../../themes/ThemeContext'; +import { InlineList } from '../List/InlineList'; /** * @internal diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx index 0bd289bd8f9..06d5666076f 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; -import { VizLegendTableProps } from './types'; -import { Icon } from '../Icon/Icon'; -import { useStyles2 } from '../../themes/ThemeContext'; import { orderBy } from 'lodash'; -import { LegendTableItem } from './VizLegendTableItem'; +import React from 'react'; + import { DisplayValue, GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '../../themes/ThemeContext'; +import { Icon } from '../Icon/Icon'; + +import { LegendTableItem } from './VizLegendTableItem'; +import { VizLegendTableProps } from './types'; + /** * @internal */ diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx index f35f7bbb712..2454ffaf38e 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx @@ -1,10 +1,13 @@ -import React, { useCallback } from 'react'; import { css, cx } from '@emotion/css'; +import React, { useCallback } from 'react'; + +import { formattedValueToString, GrafanaTheme2 } from '@grafana/data'; + +import { styleMixins } from '../../themes'; +import { useStyles2 } from '../../themes/ThemeContext'; + import { VizLegendSeriesIcon } from './VizLegendSeriesIcon'; import { VizLegendItem } from './types'; -import { useStyles2 } from '../../themes/ThemeContext'; -import { styleMixins } from '../../themes'; -import { formattedValueToString, GrafanaTheme2 } from '@grafana/data'; export interface Props { key?: React.Key; diff --git a/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx b/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx index 4ea26ba9fc8..544e8c1515f 100644 --- a/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx +++ b/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx @@ -1,5 +1,7 @@ import React, { PureComponent, CSSProperties } from 'react'; + import { VizOrientation } from '@grafana/data'; + import { calculateGridDimensions } from '../../utils/squares'; interface Props { diff --git a/packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx b/packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx index 352f0ab0093..d05577f1695 100644 --- a/packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/SeriesTable.story.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react'; +import React from 'react'; + import { SeriesTable, SeriesTableProps } from './SeriesTable'; export default { diff --git a/packages/grafana-ui/src/components/VizTooltip/SeriesTable.tsx b/packages/grafana-ui/src/components/VizTooltip/SeriesTable.tsx index 7643b7a5333..c6b43c34ccc 100644 --- a/packages/grafana-ui/src/components/VizTooltip/SeriesTable.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/SeriesTable.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { GrafanaTheme2, GraphSeriesValue } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { SeriesIcon } from '../VizLegend/SeriesIcon'; +import React from 'react'; + +import { GrafanaTheme2, GraphSeriesValue } from '@grafana/data'; + import { useStyles2 } from '../../themes'; +import { SeriesIcon } from '../VizLegend/SeriesIcon'; /** * @public diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx index ffcc3de94fc..dd3a1a6012c 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { Portal } from '../Portal/Portal'; +import React from 'react'; + import { Dimensions, TimeZone } from '@grafana/data'; -import { FlotPosition } from '../Graph/types'; -import { VizTooltipContainer } from './VizTooltipContainer'; -import { useStyles } from '../../themes'; import { TooltipDisplayMode } from '@grafana/schema'; +import { useStyles } from '../../themes'; +import { FlotPosition } from '../Graph/types'; +import { Portal } from '../Portal/Portal'; + +import { VizTooltipContainer } from './VizTooltipContainer'; + // Describes active dimensions user interacts with // It's a key-value pair where: // - key is the name of the dimension diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContainer.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContainer.tsx index 50f7f976166..5f556fbcd34 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContainer.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContainer.tsx @@ -1,9 +1,12 @@ -import React, { useState, HTMLAttributes, useMemo, useRef, useLayoutEffect } from 'react'; import { css, cx } from '@emotion/css'; +import React, { useState, HTMLAttributes, useMemo, useRef, useLayoutEffect } from 'react'; +import useWindowSize from 'react-use/lib/useWindowSize'; + +import { Dimensions2D, GrafanaTheme2 } from '@grafana/data'; + import { useStyles2 } from '../../themes'; import { getTooltipContainerStyles } from '../../themes/mixins'; -import useWindowSize from 'react-use/lib/useWindowSize'; -import { Dimensions2D, GrafanaTheme2 } from '@grafana/data'; + import { calculateTooltipPosition } from './utils'; /** diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index f4733b6ff7d..e4208a61655 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -1,3 +1,11 @@ +import { FormField } from './FormField/FormField'; +import { Input, LegacyInputStatus } from './Forms/Legacy/Input/Input'; +import { IndicatorsContainer } from './Forms/Legacy/Select/IndicatorsContainer'; +import { NoOptionsMessage } from './Forms/Legacy/Select/NoOptionsMessage'; +import { AsyncSelect, Select } from './Forms/Legacy/Select/Select'; +import { Switch } from './Forms/Legacy/Switch/Switch'; +import { SecretFormField } from './SecretFormField/SecretFormField'; + export { Icon } from './Icon/Icon'; export { IconButton, IconButtonVariant } from './IconButton/IconButton'; export { ConfirmButton } from './ConfirmButton/ConfirmButton'; @@ -42,7 +50,6 @@ export { FilterPill } from './FilterPill/FilterPill'; export { ConfirmModal, ConfirmModalProps } from './ConfirmModal/ConfirmModal'; export { QueryField } from './QueryField/QueryField'; -// Code editor export { CodeEditor } from './Monaco/CodeEditor'; export { ReactMonacoEditorLazy as ReactMonacoEditor } from './Monaco/ReactMonacoEditorLazy'; @@ -230,18 +237,6 @@ export { PluginSignatureBadge, PluginSignatureBadgeProps } from './PluginSignatu // Export this until we've figured out a good approach to inline form styles. export { InlineFormLabel } from './FormLabel/FormLabel'; -// Select -import { AsyncSelect, Select } from './Forms/Legacy/Select/Select'; -import { IndicatorsContainer } from './Forms/Legacy/Select/IndicatorsContainer'; -import { NoOptionsMessage } from './Forms/Legacy/Select/NoOptionsMessage'; - -//Input -import { Input, LegacyInputStatus } from './Forms/Legacy/Input/Input'; -import { FormField } from './FormField/FormField'; -import { SecretFormField } from './SecretFormField/SecretFormField'; - -import { Switch } from './Forms/Legacy/Switch/Switch'; - const LegacyForms = { SecretFormField, FormField, diff --git a/packages/grafana-ui/src/components/transitions/FadeTransition.tsx b/packages/grafana-ui/src/components/transitions/FadeTransition.tsx index 2a3f3ac2941..3d985302920 100644 --- a/packages/grafana-ui/src/components/transitions/FadeTransition.tsx +++ b/packages/grafana-ui/src/components/transitions/FadeTransition.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; import { CSSTransition } from 'react-transition-group'; + import { stylesFactory } from '../../themes'; const getStyles = stylesFactory((duration: number) => { diff --git a/packages/grafana-ui/src/components/transitions/SlideOutTransition.tsx b/packages/grafana-ui/src/components/transitions/SlideOutTransition.tsx index aeaf0216512..4bdac36eb2c 100644 --- a/packages/grafana-ui/src/components/transitions/SlideOutTransition.tsx +++ b/packages/grafana-ui/src/components/transitions/SlideOutTransition.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; import { CSSTransition } from 'react-transition-group'; + import { stylesFactory } from '../../themes'; const getStyles = stylesFactory((duration: number, measurement: 'width' | 'height', size: number) => { diff --git a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx index 7505c6cb26f..9fed6ed14e6 100644 --- a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx @@ -1,13 +1,15 @@ -import React from 'react'; -import { UPlotChart } from './Plot'; import { render } from '@testing-library/react'; +import createMockRaf from 'mock-raf'; +import React from 'react'; +import uPlot from 'uplot'; + import { ArrayVector, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data'; import { GraphFieldConfig, GraphDrawStyle } from '@grafana/schema'; -import uPlot from 'uplot'; -import createMockRaf from 'mock-raf'; + +import { UPlotChart } from './Plot'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; -import { preparePlotData2, getStackingGroups } from './utils'; import { SeriesProps } from './config/UPlotSeriesBuilder'; +import { preparePlotData2, getStackingGroups } from './utils'; const mockRaf = createMockRaf(); const setDataMock = jest.fn(); diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx index 7b1aba9de4a..6b4c3efd187 100755 --- a/packages/grafana-ui/src/components/uPlot/Plot.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx @@ -1,7 +1,8 @@ import React, { createRef } from 'react'; import uPlot, { AlignedData, Options } from 'uplot'; -import { DEFAULT_PLOT_CONFIG, pluginLog } from './utils'; + import { PlotProps } from './types'; +import { DEFAULT_PLOT_CONFIG, pluginLog } from './utils'; function sameDims(prevProps: PlotProps, nextProps: PlotProps) { return nextProps.width === prevProps.width && nextProps.height === prevProps.height; diff --git a/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx b/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx index ae7117765b6..188f3d93c33 100644 --- a/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx +++ b/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataFrame, DisplayValue, @@ -7,12 +8,14 @@ import { getFieldSeriesColor, reduceField, } from '@grafana/data'; -import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; -import { VizLegendItem } from '../VizLegend/types'; import { VizLegendOptions, AxisPlacement } from '@grafana/schema'; + +import { useTheme2 } from '../../themes'; import { VizLayout, VizLayoutLegendProps } from '../VizLayout/VizLayout'; import { VizLegend } from '../VizLegend/VizLegend'; -import { useTheme2 } from '../../themes'; +import { VizLegendItem } from '../VizLegend/types'; + +import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1)); diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts index aed1c014597..59b4e50b66a 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts @@ -1,8 +1,11 @@ -import { dateTimeFormat, GrafanaTheme2, isBooleanUnit, systemDateFormats, TimeZone } from '@grafana/data'; import uPlot, { Axis } from 'uplot'; -import { PlotConfigBuilder } from '../types'; -import { measureText } from '../../../utils/measureText'; + +import { dateTimeFormat, GrafanaTheme2, isBooleanUnit, systemDateFormats, TimeZone } from '@grafana/data'; import { AxisPlacement } from '@grafana/schema'; + +import { measureText } from '../../../utils/measureText'; +import { PlotConfigBuilder } from '../types'; + import { optMinMax } from './UPlotScaleBuilder'; export interface AxisProps { diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts index 9d432d46cde..a2c6f852117 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.test.ts @@ -1,6 +1,6 @@ // TODO: migrate tests below to the builder -import { UPlotConfigBuilder } from './UPlotConfigBuilder'; +import { createTheme, ThresholdsMode } from '@grafana/data'; import { GraphGradientMode, AxisPlacement, @@ -11,7 +11,8 @@ import { GraphTresholdsStyleMode, ScaleDistribution, } from '@grafana/schema'; -import { createTheme, ThresholdsMode } from '@grafana/data'; + +import { UPlotConfigBuilder } from './UPlotConfigBuilder'; describe('UPlotConfigBuilder', () => { const darkTheme = createTheme(); diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts index 95408d8ae66..cc2b2124989 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotConfigBuilder.ts @@ -1,5 +1,6 @@ -import uPlot, { Cursor, Band, Hooks, Select, AlignedData, Padding, Series } from 'uplot'; import { merge } from 'lodash'; +import uPlot, { Cursor, Band, Hooks, Select, AlignedData, Padding, Series } from 'uplot'; + import { DataFrame, DefaultTimeZone, @@ -10,12 +11,14 @@ import { TimeRange, TimeZone, } from '@grafana/data'; +import { AxisPlacement } from '@grafana/schema'; + import { FacetedData, PlotConfig, PlotTooltipInterpolator } from '../types'; +import { getStackingBands, pluginLog, StackingGroup } from '../utils'; + +import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder'; import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder'; import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder'; -import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder'; -import { AxisPlacement } from '@grafana/schema'; -import { getStackingBands, pluginLog, StackingGroup } from '../utils'; import { getThresholdsDrawHook, UPlotThresholdOptions } from './UPlotThresholds'; const cursorDefaults: Cursor = { diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts index 4491ebc4e5a..db2bb0309d1 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotScaleBuilder.ts @@ -1,7 +1,9 @@ import uPlot, { Scale, Range } from 'uplot'; -import { PlotConfigBuilder } from '../types'; -import { ScaleOrientation, ScaleDirection, ScaleDistribution } from '@grafana/schema'; + import { isBooleanUnit } from '@grafana/data'; +import { ScaleOrientation, ScaleDirection, ScaleDistribution } from '@grafana/schema'; + +import { PlotConfigBuilder } from '../types'; export interface ScaleProps { scaleKey: string; diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts index 23d823c753d..fe0c7f081f3 100755 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotSeriesBuilder.ts @@ -1,3 +1,5 @@ +import uPlot, { Series } from 'uplot'; + import { colorManipulator, DataFrameFieldIndex, @@ -7,7 +9,6 @@ import { GrafanaTheme2, ThresholdsConfig, } from '@grafana/data'; -import uPlot, { Series } from 'uplot'; import { BarAlignment, BarConfig, @@ -19,7 +20,9 @@ import { PointsConfig, VisibilityMode, } from '@grafana/schema'; + import { PlotConfigBuilder } from '../types'; + import { getHueGradientFn, getOpacityGradientFn, getScaleGradientFn } from './gradientFills'; export interface SeriesProps extends LineConfig, BarConfig, FillConfig, PointsConfig { @@ -213,7 +216,7 @@ function mapDrawStyleToPathBuilder( lineInterpolation?: LineInterpolation, barAlignment = 0, barWidthFactor = 0.6, - barMaxWidth = Infinity + barMaxWidth = 200 ): Series.PathBuilder { const pathBuilders = uPlot.paths; diff --git a/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts b/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts index a682824599f..d9e81d2b89a 100644 --- a/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts +++ b/packages/grafana-ui/src/components/uPlot/config/UPlotThresholds.ts @@ -1,9 +1,11 @@ -import { GrafanaTheme2, Threshold, ThresholdsConfig, ThresholdsMode } from '@grafana/data'; -import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '@grafana/schema'; -import { getGradientRange, scaleGradient } from './gradientFills'; import tinycolor from 'tinycolor2'; import uPlot from 'uplot'; +import { GrafanaTheme2, Threshold, ThresholdsConfig, ThresholdsMode } from '@grafana/data'; +import { GraphThresholdsStyleConfig, GraphTresholdsStyleMode } from '@grafana/schema'; + +import { getGradientRange, scaleGradient } from './gradientFills'; + export interface UPlotThresholdOptions { scaleKey: string; thresholds: ThresholdsConfig; diff --git a/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts b/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts index f1443c6d587..c61ebaefb7c 100644 --- a/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts +++ b/packages/grafana-ui/src/components/uPlot/config/gradientFills.ts @@ -1,3 +1,6 @@ +import tinycolor from 'tinycolor2'; +import uPlot from 'uplot'; + import { colorManipulator, FieldColorMode, @@ -7,8 +10,7 @@ import { ThresholdsMode, } from '@grafana/data'; import { ScaleOrientation } from '@grafana/schema'; -import tinycolor from 'tinycolor2'; -import uPlot from 'uplot'; + import { getCanvasContext } from '../../../utils/measureText'; function makeDirectionalGradient(direction: GradientDirection, bbox: uPlot.BBox, ctx: CanvasRenderingContext2D) { diff --git a/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx b/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx index 85d248fe84c..60223af3884 100644 --- a/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx +++ b/packages/grafana-ui/src/components/uPlot/geometries/EventsCanvas.tsx @@ -1,10 +1,13 @@ -import { DataFrame, DataFrameFieldIndex } from '@grafana/data'; import React, { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useMountedState } from 'react-use'; +import uPlot from 'uplot'; + +import { DataFrame, DataFrameFieldIndex } from '@grafana/data'; + import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder'; + import { Marker } from './Marker'; import { XYCanvas } from './XYCanvas'; -import uPlot from 'uplot'; interface EventsCanvasProps { id: string; diff --git a/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx b/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx index 1d5a3d015c7..34fc8ba3d26 100644 --- a/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx +++ b/packages/grafana-ui/src/components/uPlot/geometries/XYCanvas.tsx @@ -1,5 +1,5 @@ -import React, { useMemo } from 'react'; import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; interface XYCanvasProps { top: number; // css pxls diff --git a/packages/grafana-ui/src/components/uPlot/geometries/index.ts b/packages/grafana-ui/src/components/uPlot/geometries/index.ts index 15b670d70c5..1fe6ab579a1 100644 --- a/packages/grafana-ui/src/components/uPlot/geometries/index.ts +++ b/packages/grafana-ui/src/components/uPlot/geometries/index.ts @@ -1,4 +1,4 @@ -import { XYCanvas } from './XYCanvas'; -import { Marker } from './Marker'; import { EventsCanvas } from './EventsCanvas'; +import { Marker } from './Marker'; +import { XYCanvas } from './XYCanvas'; export { XYCanvas, Marker, EventsCanvas }; diff --git a/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx index 975f3b8dab2..053e99e4b08 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/KeyboardPlugin.tsx @@ -1,6 +1,7 @@ -import React, { useLayoutEffect } from 'react'; import { clamp } from 'lodash'; +import React, { useLayoutEffect } from 'react'; import uPlot from 'uplot'; + import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder'; interface KeyboardPluginProps { diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx index 15032a07a90..4b9d0ea5120 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useMountedState } from 'react-use'; import uPlot from 'uplot'; + import { arrayUtils, CartesianCoords2D, @@ -14,6 +15,7 @@ import { TimeZone, } from '@grafana/data'; import { TooltipDisplayMode, SortOrder } from '@grafana/schema'; + import { useTheme2 } from '../../../themes/ThemeContext'; import { Portal } from '../../Portal/Portal'; import { SeriesTable, SeriesTableRowProps, VizTooltipContainer } from '../../VizTooltip'; diff --git a/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx b/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx index 83308f6489c..681adb8c223 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useLayoutEffect, useState } from 'react'; + import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder'; -import { pluginLog } from '../utils'; import { PlotSelection } from '../types'; +import { pluginLog } from '../utils'; interface ZoomPluginProps { onZoom: (range: { from: number; to: number }) => void; diff --git a/packages/grafana-ui/src/components/uPlot/types.ts b/packages/grafana-ui/src/components/uPlot/types.ts index 8095b9bd262..89646a5bfc6 100755 --- a/packages/grafana-ui/src/components/uPlot/types.ts +++ b/packages/grafana-ui/src/components/uPlot/types.ts @@ -1,6 +1,8 @@ import React from 'react'; import uPlot, { Options, AlignedData } from 'uplot'; + import { TimeRange } from '@grafana/data'; + import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; export type PlotConfig = Pick< diff --git a/packages/grafana-ui/src/components/uPlot/utils.test.ts b/packages/grafana-ui/src/components/uPlot/utils.test.ts index 3ccb288de24..09df64ff81c 100644 --- a/packages/grafana-ui/src/components/uPlot/utils.test.ts +++ b/packages/grafana-ui/src/components/uPlot/utils.test.ts @@ -1,7 +1,9 @@ -import { getStackingGroups, preparePlotData2, timeFormatToTemplate } from './utils'; +import Units from 'ol/proj/Units'; + import { FieldType, MutableDataFrame } from '@grafana/data'; import { BarAlignment, GraphDrawStyle, GraphTransform, LineInterpolation, StackingMode } from '@grafana/schema'; -import Units from 'ol/proj/Units'; + +import { getStackingGroups, preparePlotData2, timeFormatToTemplate } from './utils'; describe('timeFormatToTemplate', () => { it.each` diff --git a/packages/grafana-ui/src/components/uPlot/utils.ts b/packages/grafana-ui/src/components/uPlot/utils.ts index 4bdaadc8b8d..9c76ed929a3 100755 --- a/packages/grafana-ui/src/components/uPlot/utils.ts +++ b/packages/grafana-ui/src/components/uPlot/utils.ts @@ -1,6 +1,8 @@ +import uPlot, { AlignedData, Options, PaddingSide } from 'uplot'; + import { DataFrame, ensureTimeField, FieldType } from '@grafana/data'; import { BarAlignment, GraphDrawStyle, GraphTransform, LineInterpolation, StackingMode } from '@grafana/schema'; -import uPlot, { AlignedData, Options, PaddingSide } from 'uplot'; + import { attachDebugger } from '../../utils'; import { createLogger } from '../../utils/logger'; import { buildScaleKey } from '../GraphNG/utils'; diff --git a/packages/grafana-ui/src/options/builder/axis.tsx b/packages/grafana-ui/src/options/builder/axis.tsx index 17ea669656e..bd8e7145a30 100644 --- a/packages/grafana-ui/src/options/builder/axis.tsx +++ b/packages/grafana-ui/src/options/builder/axis.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { FieldConfigEditorBuilder, FieldOverrideEditorProps, @@ -6,9 +7,10 @@ import { identityOverrideProcessor, SelectableValue, } from '@grafana/data'; -import { graphFieldOptions, Select, HorizontalGroup, RadioButtonGroup } from '../../index'; import { AxisConfig, AxisPlacement, ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; +import { graphFieldOptions, Select, HorizontalGroup, RadioButtonGroup } from '../../index'; + /** * @alpha */ @@ -143,7 +145,6 @@ const ScaleDistributionEditor: React.FC> = (props) => { const { value, onChange } = props; diff --git a/packages/grafana-ui/src/options/builder/stacking.tsx b/packages/grafana-ui/src/options/builder/stacking.tsx index 21c279448bc..cbe21614474 100644 --- a/packages/grafana-ui/src/options/builder/stacking.tsx +++ b/packages/grafana-ui/src/options/builder/stacking.tsx @@ -1,10 +1,13 @@ +import React from 'react'; + import { FieldConfigEditorBuilder, FieldOverrideEditorProps, FieldType, identityOverrideProcessor, } from '@grafana/data'; -import React from 'react'; +import { StackingConfig, StackingMode } from '@grafana/schema'; + import { GraphFieldConfig, graphFieldOptions, @@ -14,7 +17,6 @@ import { RadioButtonGroup, Tooltip, } from '../..'; -import { StackingConfig, StackingMode } from '@grafana/schema'; export const StackingEditor: React.FC> = ({ value, diff --git a/packages/grafana-ui/src/options/builder/text.tsx b/packages/grafana-ui/src/options/builder/text.tsx index fe7243bba2f..d6ecba45ebc 100644 --- a/packages/grafana-ui/src/options/builder/text.tsx +++ b/packages/grafana-ui/src/options/builder/text.tsx @@ -1,5 +1,5 @@ -import { OptionsWithTextFormatting } from '@grafana/schema'; import { PanelOptionsEditorBuilder } from '@grafana/data'; +import { OptionsWithTextFormatting } from '@grafana/schema'; /** * Adds common text control options to a visualization options diff --git a/packages/grafana-ui/src/options/builder/tooltip.tsx b/packages/grafana-ui/src/options/builder/tooltip.tsx index 3136c77ef2f..c58f565cecf 100644 --- a/packages/grafana-ui/src/options/builder/tooltip.tsx +++ b/packages/grafana-ui/src/options/builder/tooltip.tsx @@ -1,5 +1,5 @@ -import { OptionsWithTooltip, TooltipDisplayMode, SortOrder } from '@grafana/schema'; import { PanelOptionsEditorBuilder } from '@grafana/data'; +import { OptionsWithTooltip, TooltipDisplayMode, SortOrder } from '@grafana/schema'; export function addTooltipOptions( builder: PanelOptionsEditorBuilder, diff --git a/packages/grafana-ui/src/slate-plugins/braces.test.tsx b/packages/grafana-ui/src/slate-plugins/braces.test.tsx index ecd55b7f503..680e26f3a32 100644 --- a/packages/grafana-ui/src/slate-plugins/braces.test.tsx +++ b/packages/grafana-ui/src/slate-plugins/braces.test.tsx @@ -1,7 +1,9 @@ +import { shallow } from 'enzyme'; import React from 'react'; import Plain from 'slate-plain-serializer'; + import { Editor } from '@grafana/slate-react'; -import { shallow } from 'enzyme'; + import { BracesPlugin } from './braces'; declare global { diff --git a/packages/grafana-ui/src/slate-plugins/braces.ts b/packages/grafana-ui/src/slate-plugins/braces.ts index dae974be82b..a98071c9bf8 100644 --- a/packages/grafana-ui/src/slate-plugins/braces.ts +++ b/packages/grafana-ui/src/slate-plugins/braces.ts @@ -1,7 +1,8 @@ -import { Plugin } from '@grafana/slate-react'; import { Editor as CoreEditor, Annotation } from 'slate'; import { v4 as uuidv4 } from 'uuid'; +import { Plugin } from '@grafana/slate-react'; + const BRACES: any = { '[': ']', '{': '}', diff --git a/packages/grafana-ui/src/slate-plugins/clear.test.tsx b/packages/grafana-ui/src/slate-plugins/clear.test.tsx index d1643010806..20f5bc2893c 100644 --- a/packages/grafana-ui/src/slate-plugins/clear.test.tsx +++ b/packages/grafana-ui/src/slate-plugins/clear.test.tsx @@ -1,7 +1,9 @@ -import Plain from 'slate-plain-serializer'; -import React from 'react'; -import { Editor } from '@grafana/slate-react'; import { shallow } from 'enzyme'; +import React from 'react'; +import Plain from 'slate-plain-serializer'; + +import { Editor } from '@grafana/slate-react'; + import { ClearPlugin } from './clear'; describe('clear', () => { diff --git a/packages/grafana-ui/src/slate-plugins/clear.ts b/packages/grafana-ui/src/slate-plugins/clear.ts index c711f68aff1..208aa088e8f 100644 --- a/packages/grafana-ui/src/slate-plugins/clear.ts +++ b/packages/grafana-ui/src/slate-plugins/clear.ts @@ -1,6 +1,7 @@ -import { Plugin } from '@grafana/slate-react'; import { Editor as CoreEditor } from 'slate'; +import { Plugin } from '@grafana/slate-react'; + // Clears the rest of the line after the caret export function ClearPlugin(): Plugin { return { diff --git a/packages/grafana-ui/src/slate-plugins/clipboard.ts b/packages/grafana-ui/src/slate-plugins/clipboard.ts index ee558b288b4..6a20e4d4a5c 100644 --- a/packages/grafana-ui/src/slate-plugins/clipboard.ts +++ b/packages/grafana-ui/src/slate-plugins/clipboard.ts @@ -1,6 +1,7 @@ -import { Plugin } from '@grafana/slate-react'; import { Editor as CoreEditor } from 'slate'; +import { Plugin } from '@grafana/slate-react'; + const getCopiedText = (textBlocks: string[], startOffset: number, endOffset: number) => { if (!textBlocks.length) { return undefined; diff --git a/packages/grafana-ui/src/slate-plugins/indentation.ts b/packages/grafana-ui/src/slate-plugins/indentation.ts index b848ccfd82f..41a941c9ef6 100644 --- a/packages/grafana-ui/src/slate-plugins/indentation.ts +++ b/packages/grafana-ui/src/slate-plugins/indentation.ts @@ -1,6 +1,7 @@ -import { RangeJSON, Range as SlateRange, Editor as CoreEditor } from 'slate'; -import { Plugin } from '@grafana/slate-react'; import { isKeyHotkey } from 'is-hotkey'; +import { RangeJSON, Range as SlateRange, Editor as CoreEditor } from 'slate'; + +import { Plugin } from '@grafana/slate-react'; const isIndentLeftHotkey = isKeyHotkey('mod+['); const isShiftTabHotkey = isKeyHotkey('shift+tab'); diff --git a/packages/grafana-ui/src/slate-plugins/newline.ts b/packages/grafana-ui/src/slate-plugins/newline.ts index b987b485917..62c1dc1d485 100644 --- a/packages/grafana-ui/src/slate-plugins/newline.ts +++ b/packages/grafana-ui/src/slate-plugins/newline.ts @@ -1,6 +1,7 @@ -import { Plugin } from '@grafana/slate-react'; import { Editor as CoreEditor } from 'slate'; +import { Plugin } from '@grafana/slate-react'; + function getIndent(text: string) { let offset = text.length - text.trimLeft().length; if (offset) { diff --git a/packages/grafana-ui/src/slate-plugins/runner.test.tsx b/packages/grafana-ui/src/slate-plugins/runner.test.tsx index 6041d1bc5d5..aa482f9b1bf 100644 --- a/packages/grafana-ui/src/slate-plugins/runner.test.tsx +++ b/packages/grafana-ui/src/slate-plugins/runner.test.tsx @@ -1,7 +1,9 @@ -import Plain from 'slate-plain-serializer'; -import React from 'react'; -import { Editor } from '@grafana/slate-react'; import { shallow } from 'enzyme'; +import React from 'react'; +import Plain from 'slate-plain-serializer'; + +import { Editor } from '@grafana/slate-react'; + import { RunnerPlugin } from './runner'; describe('runner', () => { diff --git a/packages/grafana-ui/src/slate-plugins/runner.ts b/packages/grafana-ui/src/slate-plugins/runner.ts index a326af8d252..9c21f7b466e 100644 --- a/packages/grafana-ui/src/slate-plugins/runner.ts +++ b/packages/grafana-ui/src/slate-plugins/runner.ts @@ -1,6 +1,7 @@ -import { Plugin } from '@grafana/slate-react'; import { Editor as CoreEditor } from 'slate'; +import { Plugin } from '@grafana/slate-react'; + export function RunnerPlugin({ handler }: any): Plugin { return { onKeyDown(event: Event, editor: CoreEditor, next: Function) { diff --git a/packages/grafana-ui/src/slate-plugins/selection_shortcuts.ts b/packages/grafana-ui/src/slate-plugins/selection_shortcuts.ts index 23211f356ab..54af62f3a24 100644 --- a/packages/grafana-ui/src/slate-plugins/selection_shortcuts.ts +++ b/packages/grafana-ui/src/slate-plugins/selection_shortcuts.ts @@ -1,7 +1,7 @@ -import { Plugin } from '@grafana/slate-react'; +import { isKeyHotkey } from 'is-hotkey'; import { Editor as CoreEditor } from 'slate'; -import { isKeyHotkey } from 'is-hotkey'; +import { Plugin } from '@grafana/slate-react'; const isSelectLineHotkey = isKeyHotkey('mod+l'); diff --git a/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts b/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts index 475ffd33d83..2062d9347f5 100644 --- a/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts +++ b/packages/grafana-ui/src/slate-plugins/slate-prism/index.ts @@ -1,8 +1,10 @@ import Prism, { LanguageMap } from 'prismjs'; import { Block, Text, Decoration } from 'slate'; + import { Plugin } from '@grafana/slate-react'; -import Options, { OptionsFormat } from './options'; + import TOKEN_MARK from './TOKEN_MARK'; +import Options, { OptionsFormat } from './options'; export interface Token { content: string; diff --git a/packages/grafana-ui/src/slate-plugins/slate-prism/options.tsx b/packages/grafana-ui/src/slate-plugins/slate-prism/options.tsx index cc95ddbd3c9..c698f29cf42 100644 --- a/packages/grafana-ui/src/slate-plugins/slate-prism/options.tsx +++ b/packages/grafana-ui/src/slate-plugins/slate-prism/options.tsx @@ -1,7 +1,8 @@ +import { Record } from 'immutable'; import React from 'react'; import { Mark, Node, Decoration } from 'slate'; + import { Editor } from '@grafana/slate-react'; -import { Record } from 'immutable'; import TOKEN_MARK from './TOKEN_MARK'; diff --git a/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx b/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx index 36b6a688bae..aed96f1e346 100644 --- a/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx +++ b/packages/grafana-ui/src/slate-plugins/suggestions.test.tsx @@ -1,11 +1,13 @@ -import { SearchFunctionMap } from '../utils/searchFunctions'; import { render } from 'enzyme'; -import { SuggestionsPlugin } from './suggestions'; +import _ from 'lodash'; // eslint-disable-line lodash/import-scope + import { Plugin as SlatePlugin } from '@grafana/slate-react'; -import { SearchFunctionType } from '../utils'; + import { CompletionItemGroup, SuggestionsState } from '../types'; -// eslint-disable-next-line lodash/import-scope -import _ from 'lodash'; +import { SearchFunctionType } from '../utils'; +import { SearchFunctionMap } from '../utils/searchFunctions'; + +import { SuggestionsPlugin } from './suggestions'; jest.spyOn(_, 'debounce').mockImplementation((func: (...args: any) => any) => { return Object.assign(func, { cancel: jest.fn(), flush: jest.fn() }); diff --git a/packages/grafana-ui/src/slate-plugins/suggestions.tsx b/packages/grafana-ui/src/slate-plugins/suggestions.tsx index d04155bdf72..20ccb852262 100644 --- a/packages/grafana-ui/src/slate-plugins/suggestions.tsx +++ b/packages/grafana-ui/src/slate-plugins/suggestions.tsx @@ -1,15 +1,16 @@ -import React from 'react'; import { debounce, sortBy } from 'lodash'; - +import React from 'react'; import { Editor as CoreEditor } from 'slate'; + import { Plugin as SlatePlugin } from '@grafana/slate-react'; -import TOKEN_MARK from './slate-prism/TOKEN_MARK'; import { Typeahead } from '../components/Typeahead/Typeahead'; import { CompletionItem, SuggestionsState, TypeaheadInput, TypeaheadOutput } from '../types'; import { makeFragment, SearchFunctionType } from '../utils'; import { SearchFunctionMap } from '../utils/searchFunctions'; +import TOKEN_MARK from './slate-prism/TOKEN_MARK'; + export const TYPEAHEAD_DEBOUNCE = 250; // Commands added to the editor by this plugin. diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx index 6ac89b14bc1..fb95b5efdca 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx +++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { Global } from '@emotion/react'; +import React from 'react'; + import { useTheme2 } from '..'; -import { getElementStyles } from './elements'; -import { getCardStyles } from './card'; + import { getAgularPanelStyles } from './angularPanelStyles'; -import { getPageStyles } from './page'; +import { getCardStyles } from './card'; +import { getElementStyles } from './elements'; import { getMarkdownStyles } from './markdownStyles'; +import { getPageStyles } from './page'; /** @internal */ export function GlobalStyles() { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/angularPanelStyles.ts b/packages/grafana-ui/src/themes/GlobalStyles/angularPanelStyles.ts index 1e9608e89c0..adb3088ac45 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/angularPanelStyles.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/angularPanelStyles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; + import { GrafanaTheme2 } from '@grafana/data'; export function getAgularPanelStyles(theme: GrafanaTheme2) { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/card.ts b/packages/grafana-ui/src/themes/GlobalStyles/card.ts index b3d820d2910..35b356f5e9f 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/card.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/card.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; + import { GrafanaTheme2 } from '@grafana/data'; export function getCardStyles(theme: GrafanaTheme2) { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts index 8f2580de8bb..c987a5e55e2 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts @@ -1,5 +1,7 @@ import { css } from '@emotion/react'; + import { GrafanaTheme2, ThemeTypographyVariant } from '@grafana/data'; + import { getFocusStyles } from '../mixins'; export function getElementStyles(theme: GrafanaTheme2) { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/markdownStyles.ts b/packages/grafana-ui/src/themes/GlobalStyles/markdownStyles.ts index 41ae1a23ea9..67c096075e7 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/markdownStyles.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/markdownStyles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; + import { GrafanaTheme2 } from '@grafana/data'; export function getMarkdownStyles(theme: GrafanaTheme2) { diff --git a/packages/grafana-ui/src/themes/GlobalStyles/page.ts b/packages/grafana-ui/src/themes/GlobalStyles/page.ts index 1be34dcb752..b0b961da430 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/page.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/page.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; + import { GrafanaTheme2 } from '@grafana/data'; export function getPageStyles(theme: GrafanaTheme2) { diff --git a/packages/grafana-ui/src/themes/ThemeContext.test.tsx b/packages/grafana-ui/src/themes/ThemeContext.test.tsx index a95f5b1c4f8..098dee97c9e 100644 --- a/packages/grafana-ui/src/themes/ThemeContext.test.tsx +++ b/packages/grafana-ui/src/themes/ThemeContext.test.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; import { css } from '@emotion/css'; -import { mockThemeContext, useStyles } from './ThemeContext'; import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; + +import { mockThemeContext, useStyles } from './ThemeContext'; describe('useStyles', () => { it('memoizes the passed in function correctly', () => { diff --git a/packages/grafana-ui/src/themes/ThemeContext.tsx b/packages/grafana-ui/src/themes/ThemeContext.tsx index 1e0ef779ff8..be6d0f4785b 100644 --- a/packages/grafana-ui/src/themes/ThemeContext.tsx +++ b/packages/grafana-ui/src/themes/ThemeContext.tsx @@ -1,7 +1,10 @@ -import { createTheme, GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; import hoistNonReactStatics from 'hoist-non-react-statics'; import React, { useContext } from 'react'; + +import { createTheme, GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; + import { Themeable, Themeable2 } from '../types/theme'; + import { stylesFactory } from './stylesFactory'; type Omit = Pick>; diff --git a/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts b/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts index e4ee63b4af2..50c4b052857 100644 --- a/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts +++ b/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts @@ -1,6 +1,7 @@ /* eslint-disable max-len */ import { GrafanaTheme2 } from '@grafana/data'; + import { renderGeneratedFileBanner } from '../utils/generatedFileBanner'; export const darkThemeVarsTemplate = (theme: GrafanaTheme2) => diff --git a/packages/grafana-ui/src/themes/_variables.light.scss.tmpl.ts b/packages/grafana-ui/src/themes/_variables.light.scss.tmpl.ts index 196faabe1df..bd8887c9889 100644 --- a/packages/grafana-ui/src/themes/_variables.light.scss.tmpl.ts +++ b/packages/grafana-ui/src/themes/_variables.light.scss.tmpl.ts @@ -1,9 +1,11 @@ /* eslint-disable max-len */ -import { renderGeneratedFileBanner } from '../utils/generatedFileBanner'; -import { styleMixins } from '.'; import { GrafanaTheme2 } from '@grafana/data'; +import { renderGeneratedFileBanner } from '../utils/generatedFileBanner'; + +import { styleMixins } from '.'; + export const lightThemeVarsTemplate = (theme: GrafanaTheme2) => `${renderGeneratedFileBanner('grafana-ui/src/themes/light.ts', 'grafana-ui/src/themes/_variable.light.scss.tmpl.ts')} // Global values diff --git a/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts b/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts index dab9f57cc79..6d6d4cde84a 100644 --- a/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts +++ b/packages/grafana-ui/src/themes/_variables.scss.tmpl.ts @@ -1,6 +1,7 @@ /* eslint-disable max-len */ import { GrafanaTheme2 } from '@grafana/data'; + import { renderGeneratedFileBanner } from '../utils/generatedFileBanner'; export const commonThemeVarsTemplate = (theme: GrafanaTheme2) => diff --git a/packages/grafana-ui/src/themes/mixins.ts b/packages/grafana-ui/src/themes/mixins.ts index 243e582f42c..b57cdbe8b21 100644 --- a/packages/grafana-ui/src/themes/mixins.ts +++ b/packages/grafana-ui/src/themes/mixins.ts @@ -1,7 +1,8 @@ import { CSSObject } from '@emotion/css'; -import { GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; import tinycolor from 'tinycolor2'; +import { GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; + export function cardChrome(theme: GrafanaTheme2): string { return ` background: ${theme.colors.background.secondary}; diff --git a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts index 4057488320f..974e3cc8889 100644 --- a/packages/grafana-ui/src/themes/selectThemeVariant.test.ts +++ b/packages/grafana-ui/src/themes/selectThemeVariant.test.ts @@ -1,5 +1,7 @@ import { GrafanaThemeType } from '@grafana/data'; + import { selectThemeVariant } from './selectThemeVariant'; + import { mockTheme } from './index'; const lightThemeMock = { diff --git a/packages/grafana-ui/src/types/completion.ts b/packages/grafana-ui/src/types/completion.ts index 1855889ff1e..f8e012eab81 100644 --- a/packages/grafana-ui/src/types/completion.ts +++ b/packages/grafana-ui/src/types/completion.ts @@ -1,4 +1,5 @@ import { Value, Editor as CoreEditor } from 'slate'; + import { SearchFunctionType } from '../utils'; /** diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index 870cf8b2d82..0aee8ac145e 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -1,4 +1,5 @@ import { Field, FieldType } from '@grafana/data'; + import { ComponentSize } from './size'; export type IconType = 'mono' | 'default' | 'solid'; export type IconSize = ComponentSize | 'xl' | 'xxl' | 'xxxl'; diff --git a/packages/grafana-ui/src/utils/dataLinks.ts b/packages/grafana-ui/src/utils/dataLinks.ts index b99e50e089d..42c57622e08 100644 --- a/packages/grafana-ui/src/utils/dataLinks.ts +++ b/packages/grafana-ui/src/utils/dataLinks.ts @@ -1,4 +1,5 @@ import { LinkModel } from '@grafana/data'; + import { MenuItemProps } from '../components/Menu/MenuItem'; import { IconName } from '../types'; diff --git a/packages/grafana-ui/src/utils/fuzzy.ts b/packages/grafana-ui/src/utils/fuzzy.ts index 69d47d5167f..418a9efe182 100644 --- a/packages/grafana-ui/src/utils/fuzzy.ts +++ b/packages/grafana-ui/src/utils/fuzzy.ts @@ -1,6 +1,7 @@ -import { HighlightPart } from '../types'; import { last } from 'lodash'; +import { HighlightPart } from '../types'; + type FuzzyMatch = { /** * Total number of unmatched letters between matched letters diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts index cecdb086f2b..b4ec365d717 100644 --- a/packages/grafana-ui/src/utils/index.ts +++ b/packages/grafana-ui/src/utils/index.ts @@ -1,3 +1,6 @@ +import * as DOMUtil from './dom'; // includes Element.closest polyfill +import * as ReactUtils from './reactUtils'; + export * from './colors'; export * from './validate'; export * from './slate'; @@ -10,7 +13,6 @@ export * from './useForceUpdate'; export { SearchFunctionType } from './searchFunctions'; export { ansicolor } from 'ansicolor'; -import * as DOMUtil from './dom'; // includes Element.closest polyfill export { DOMUtil }; export { createLogger } from './logger'; @@ -18,6 +20,4 @@ export { attachDebugger } from './debug'; export * from './nodeGraph'; export { fuzzyMatch } from './fuzzy'; -// React utils -import * as ReactUtils from './reactUtils'; export { ReactUtils }; diff --git a/packages/grafana-ui/src/utils/searchFunctions.ts b/packages/grafana-ui/src/utils/searchFunctions.ts index 677b2d06454..02cce0ff35f 100644 --- a/packages/grafana-ui/src/utils/searchFunctions.ts +++ b/packages/grafana-ui/src/utils/searchFunctions.ts @@ -1,4 +1,5 @@ import { CompletionItem, SearchFunction } from '../types'; + import { fuzzyMatch } from './fuzzy'; /** diff --git a/packages/grafana-ui/src/utils/standardEditors.tsx b/packages/grafana-ui/src/utils/standardEditors.tsx index 065c1812113..07948c2ae54 100644 --- a/packages/grafana-ui/src/utils/standardEditors.tsx +++ b/packages/grafana-ui/src/utils/standardEditors.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataLink, dataLinksOverrideProcessor, @@ -19,7 +20,6 @@ import { FieldNamePickerConfigSettings, } from '@grafana/data'; -import { Switch } from '../components/Switch/Switch'; import { NumberValueEditor, SliderValueEditor, @@ -30,12 +30,13 @@ import { MultiSelectValueEditor, TimeZonePicker, } from '../components'; -import { UnitValueEditor } from '../components/OptionsUI/units'; -import { DataLinksValueEditor } from '../components/OptionsUI/links'; +import { FieldNamePicker } from '../components/MatchersUI/FieldNamePicker'; import { ColorValueEditor } from '../components/OptionsUI/color'; import { FieldColorEditor } from '../components/OptionsUI/fieldColor'; +import { DataLinksValueEditor } from '../components/OptionsUI/links'; import { StatsPickerEditor } from '../components/OptionsUI/stats'; -import { FieldNamePicker } from '../components/MatchersUI/FieldNamePicker'; +import { UnitValueEditor } from '../components/OptionsUI/units'; +import { Switch } from '../components/Switch/Switch'; /** * Returns collection of common field config properties definitions diff --git a/packages/grafana-ui/src/utils/storybook/DashboardStoryCanvas.tsx b/packages/grafana-ui/src/utils/storybook/DashboardStoryCanvas.tsx index 9a7adfdfc9a..435d1158c3d 100644 --- a/packages/grafana-ui/src/utils/storybook/DashboardStoryCanvas.tsx +++ b/packages/grafana-ui/src/utils/storybook/DashboardStoryCanvas.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { useTheme } from '../../themes'; export interface Props { diff --git a/packages/grafana-ui/src/utils/storybook/StoryExample.tsx b/packages/grafana-ui/src/utils/storybook/StoryExample.tsx index 432cd7ddb50..192d4130357 100644 --- a/packages/grafana-ui/src/utils/storybook/StoryExample.tsx +++ b/packages/grafana-ui/src/utils/storybook/StoryExample.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { useTheme } from '../../themes/ThemeContext'; export interface Props { diff --git a/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx b/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx index 9fffdc9cbf4..095d1f3ed25 100644 --- a/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx +++ b/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx @@ -1,8 +1,9 @@ // This is a temporary workaround to allow theme switching storybook docs // see https://github.com/storybookjs/storybook/issues/10523 for further details -import React from 'react'; import { DocsContainer } from '@storybook/addon-docs/blocks'; +import React from 'react'; import { useDarkMode } from 'storybook-dark-mode'; + import { GrafanaLight, GrafanaDark } from '../../../.storybook/storybookTheme'; type Props = { diff --git a/packages/grafana-ui/src/utils/storybook/UseState.tsx b/packages/grafana-ui/src/utils/storybook/UseState.tsx index 7e8340b979e..69d6667d135 100644 --- a/packages/grafana-ui/src/utils/storybook/UseState.tsx +++ b/packages/grafana-ui/src/utils/storybook/UseState.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import { action } from '@storybook/addon-actions'; +import React from 'react'; interface StateHolderProps { logState?: boolean; diff --git a/packages/grafana-ui/src/utils/storybook/knobs.ts b/packages/grafana-ui/src/utils/storybook/knobs.ts index 03f729bbb3e..f9fe8152b69 100644 --- a/packages/grafana-ui/src/utils/storybook/knobs.ts +++ b/packages/grafana-ui/src/utils/storybook/knobs.ts @@ -1,4 +1,5 @@ import { select } from '@storybook/addon-knobs'; + import { getAvailableIcons } from '../../types'; const VISUAL_GROUP = 'Visual options'; diff --git a/packages/grafana-ui/src/utils/storybook/withCenteredStory.tsx b/packages/grafana-ui/src/utils/storybook/withCenteredStory.tsx index a30350c96ab..c8937f5c648 100644 --- a/packages/grafana-ui/src/utils/storybook/withCenteredStory.tsx +++ b/packages/grafana-ui/src/utils/storybook/withCenteredStory.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { RenderFunction } from '../../types'; interface CenteredStoryProps { diff --git a/packages/grafana-ui/src/utils/storybook/withPaddedStory.tsx b/packages/grafana-ui/src/utils/storybook/withPaddedStory.tsx index f5778b35814..a26f6f4f3ce 100644 --- a/packages/grafana-ui/src/utils/storybook/withPaddedStory.tsx +++ b/packages/grafana-ui/src/utils/storybook/withPaddedStory.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { GlobalStyles, useTheme2 } from '../../themes'; import { RenderFunction } from '../../types'; diff --git a/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx b/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx index 44e238e6eb9..9168ad1014b 100644 --- a/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx +++ b/packages/grafana-ui/src/utils/storybook/withRightAlignedStory.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { RenderFunction } from '../../types'; const RightAlignedStory: React.FunctionComponent<{}> = ({ children }) => { diff --git a/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx b/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx index b13c0ecd42a..ac836367e4b 100644 --- a/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx +++ b/packages/grafana-ui/src/utils/storybook/withStoryContainer.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; -import { RenderFunction } from '../../types'; import { StoryContext } from '@storybook/react'; +import React from 'react'; + +import { RenderFunction } from '../../types'; const StoryContainer: React.FC<{ width?: number; height?: number; showBoundaries: boolean }> = ({ children, diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx index b6a23326e6f..2580acf3b6b 100644 --- a/packages/grafana-ui/src/utils/storybook/withTheme.tsx +++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import { ThemeContext } from '../../themes/ThemeContext'; -import { createTheme, GrafanaTheme2 } from '@grafana/data'; -import { RenderFunction } from '../../types'; import { useDarkMode } from 'storybook-dark-mode'; + +import { createTheme, GrafanaTheme2 } from '@grafana/data'; + import { GlobalStyles } from '../../themes/GlobalStyles/GlobalStyles'; +import { ThemeContext } from '../../themes/ThemeContext'; +import { RenderFunction } from '../../types'; type SassThemeChangeHandler = (theme: GrafanaTheme2) => void; const ThemeableStory: React.FunctionComponent<{ handleSassThemeChange: SassThemeChangeHandler }> = ({ diff --git a/packages/grafana-ui/src/utils/table.ts b/packages/grafana-ui/src/utils/table.ts index b70bc6af1bc..80d346ab27c 100644 --- a/packages/grafana-ui/src/utils/table.ts +++ b/packages/grafana-ui/src/utils/table.ts @@ -1,7 +1,8 @@ -import { Field, LinkModel } from '@grafana/data'; import { MouseEventHandler } from 'react'; import { Row } from 'react-table'; +import { Field, LinkModel } from '@grafana/data'; + /** * @internal */ diff --git a/packages/grafana-ui/src/utils/typeahead.ts b/packages/grafana-ui/src/utils/typeahead.ts index 980cbe63a61..856b4f8d305 100644 --- a/packages/grafana-ui/src/utils/typeahead.ts +++ b/packages/grafana-ui/src/utils/typeahead.ts @@ -1,7 +1,9 @@ import { default as calculateSize } from 'calculate-size'; -import { CompletionItemGroup, CompletionItem, CompletionItemKind } from '../types/completion'; + import { GrafanaTheme } from '@grafana/data'; +import { CompletionItemGroup, CompletionItem, CompletionItemKind } from '../types/completion'; + export const flattenGroupItems = (groupedItems: CompletionItemGroup[]): CompletionItem[] => { return groupedItems.reduce((all: CompletionItem[], { items, label }) => { all.push({ diff --git a/packages/grafana-ui/src/utils/useDelayedSwitch.story.internal.tsx b/packages/grafana-ui/src/utils/useDelayedSwitch.story.internal.tsx index ef26cf70384..df5204f74b7 100644 --- a/packages/grafana-ui/src/utils/useDelayedSwitch.story.internal.tsx +++ b/packages/grafana-ui/src/utils/useDelayedSwitch.story.internal.tsx @@ -1,8 +1,8 @@ +import { Story } from '@storybook/react'; import React from 'react'; import { withCenteredStory } from './storybook/withCenteredStory'; import { useDelayedSwitch } from './useDelayedSwitch'; -import { Story } from '@storybook/react'; export default { title: 'useDelayedSwitch', diff --git a/packages/jaeger-ui-components/src/ScrollManager.test.js b/packages/jaeger-ui-components/src/ScrollManager.test.js index 273b3b9d3e1..f09cebe7c25 100644 --- a/packages/jaeger-ui-components/src/ScrollManager.test.js +++ b/packages/jaeger-ui-components/src/ScrollManager.test.js @@ -14,8 +14,8 @@ jest.mock('./scroll-page'); -import { scrollBy, scrollTo } from './scroll-page'; import ScrollManager from './ScrollManager'; +import { scrollBy, scrollTo } from './scroll-page'; const SPAN_HEIGHT = 2; diff --git a/packages/jaeger-ui-components/src/Theme.tsx b/packages/jaeger-ui-components/src/Theme.tsx index 45cc4847a63..e0213d6ba0f 100644 --- a/packages/jaeger-ui-components/src/Theme.tsx +++ b/packages/jaeger-ui-components/src/Theme.tsx @@ -13,6 +13,7 @@ // limitations under the License. import tinycolor from 'tinycolor2'; + import { GrafanaTheme2 } from '@grafana/data'; /** diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.test.js b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.test.js index 6799d5290e6..cdb5e943cf0 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.test.js @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { createTheme } from '@grafana/data'; import { UnthemedCanvasSpanGraph } from './CanvasSpanGraph'; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx index 2e62d4ad3b8..a0454c80009 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; +import * as React from 'react'; -import renderIntoCanvas from './render-into-canvas'; -import { getRgbColorByKey } from '../../utils/color-generator'; -import { TNil } from '../../types'; +import { GrafanaTheme2 } from '@grafana/data'; +import { withTheme2, stylesFactory } from '@grafana/ui'; import { autoColor } from '../../Theme'; -import { withTheme2, stylesFactory } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; +import { TNil } from '../../types'; +import { getRgbColorByKey } from '../../utils/color-generator'; + +import renderIntoCanvas from './render-into-canvas'; const getStyles = stylesFactory((theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.test.js b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.test.js index 9f1352dfcb2..77736bee6e0 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import GraphTicks from './GraphTicks'; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.tsx index d69c82e17ad..145db1bf459 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/GraphTicks.tsx @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { useStyles2 } from '@grafana/ui'; const getStyles = () => { diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.test.js b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.test.js index afe6525f464..88943f3dd27 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import sinon from 'sinon'; import Scrubber, { getStyles } from './Scrubber'; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.tsx index 9242819e1fa..b4cd86699d4 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/Scrubber.tsx @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; -import cx from 'classnames'; import { css } from '@emotion/css'; +import cx from 'classnames'; +import React from 'react'; + import { useStyles2 } from '@grafana/ui'; export const getStyles = () => { diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.test.js b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.test.js index 619a0ee8b9d..595b5da7586 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import TickLabels from './TickLabels'; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx index 188b36cc900..6a81881a257 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/TickLabels.tsx @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { useStyles2 } from '@grafana/ui'; import { formatDuration } from '../../utils/date'; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js index 3530d9602a3..b65184021ed 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.test.js @@ -14,13 +14,15 @@ import { shallow } from 'enzyme'; import React from 'react'; + import { createTheme } from '@grafana/data'; +import { EUpdateTypes } from '../../utils/DraggableManager'; +import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; + import GraphTicks from './GraphTicks'; import Scrubber from './Scrubber'; import ViewingLayer, { dragTypes, getStyles } from './ViewingLayer'; -import { EUpdateTypes } from '../../utils/DraggableManager'; -import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; function getViewRange(viewStart, viewEnd) { return { diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.tsx index 722fbc8a7d9..e4090032a4c 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/ViewingLayer.tsx @@ -12,18 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { css } from '@emotion/css'; import cx from 'classnames'; import * as React from 'react'; -import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; import { withTheme2, stylesFactory, Button } from '@grafana/ui'; -import GraphTicks from './GraphTicks'; -import Scrubber from './Scrubber'; import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate, TNil } from '../..'; import { autoColor } from '../../Theme'; import DraggableManager, { DraggableBounds, DraggingUpdate, EUpdateTypes } from '../../utils/DraggableManager'; +import GraphTicks from './GraphTicks'; +import Scrubber from './Scrubber'; + export const getStyles = stylesFactory((theme: GrafanaTheme2) => { // Need this cause emotion will merge emotion generated classes into single className if used with cx from emotion // package and the selector won't work diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js index 4ef74638ee6..d76f4aab944 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.test.js @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; -import CanvasSpanGraph from './CanvasSpanGraph'; -import SpanGraph from './index'; -import TickLabels from './TickLabels'; -import ViewingLayer from './ViewingLayer'; import traceGenerator from '../../demo/trace-generators'; import transformTraceData from '../../model/transform-trace-data'; import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; +import CanvasSpanGraph from './CanvasSpanGraph'; +import TickLabels from './TickLabels'; +import ViewingLayer from './ViewingLayer'; + +import SpanGraph from './index'; + describe('', () => { polyfillAnimationFrame(window); diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx index 4ddae074538..6bab52a4b67 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import cx from 'classnames'; import memoizeOne from 'memoize-one'; +import * as React from 'react'; + +import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../..'; +import { TraceSpan, Trace } from '../../types/trace'; +import { ubPb2, ubPx2, ubRelative } from '../../uberUtilityStyles'; import CanvasSpanGraph from './CanvasSpanGraph'; import TickLabels from './TickLabels'; import ViewingLayer from './ViewingLayer'; -import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../..'; -import { TraceSpan, Trace } from '../../types/trace'; -import { ubPb2, ubPx2, ubRelative } from '../../uberUtilityStyles'; const DEFAULT_HEIGHT = 60; const TIMELINE_TICK_INTERVAL = 4; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js b/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js index e455fa1f360..e5542b5ea76 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow, mount } from 'enzyme'; +import React from 'react'; -import SpanGraph from './SpanGraph'; -import TracePageHeader, { HEADER_ITEMS } from './TracePageHeader'; import LabeledList from '../common/LabeledList'; import traceGenerator from '../demo/trace-generators'; import { getTraceName } from '../model/trace-viewer'; import transformTraceData from '../model/transform-trace-data'; +import SpanGraph from './SpanGraph'; +import TracePageHeader, { HEADER_ITEMS } from './TracePageHeader'; + describe('', () => { const trace = transformTraceData(traceGenerator.trace({})); const defaultProps = { diff --git a/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx b/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx index 2deb6921592..dd476a85046 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx @@ -12,25 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; -import { get as _get, maxBy as _maxBy, values as _values } from 'lodash'; -import MdKeyboardArrowRight from 'react-icons/lib/md/keyboard-arrow-right'; import { css } from '@emotion/css'; import cx from 'classnames'; +import { get as _get, maxBy as _maxBy, values as _values } from 'lodash'; +import * as React from 'react'; +import MdKeyboardArrowRight from 'react-icons/lib/md/keyboard-arrow-right'; + import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import SpanGraph from './SpanGraph'; import { autoColor, TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '..'; +import ExternalLinks from '../common/ExternalLinks'; import LabeledList from '../common/LabeledList'; import TraceName from '../common/TraceName'; +import { getTraceLinks } from '../model/link-patterns'; import { getTraceName } from '../model/trace-viewer'; import { Trace } from '../types/trace'; -import { formatDuration } from '../utils/date'; -import { getTraceLinks } from '../model/link-patterns'; - -import ExternalLinks from '../common/ExternalLinks'; import { uTxMuted } from '../uberUtilityStyles'; +import { formatDuration } from '../utils/date'; + +import SpanGraph from './SpanGraph'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js b/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js index 47b4e916cde..d7cf4baff07 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js +++ b/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js @@ -12,14 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { createTheme } from '@grafana/data'; -import * as markers from './TracePageSearchBar.markers'; -import TracePageSearchBar, { getStyles } from './TracePageSearchBar'; import UiFindInput from '../common/UiFindInput'; +import TracePageSearchBar, { getStyles } from './TracePageSearchBar'; +import * as markers from './TracePageSearchBar.markers'; + const defaultProps = { forwardedRef: React.createRef(), navigable: true, diff --git a/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.tsx b/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.tsx index 4eef52b5ef0..cfa1e99a544 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.tsx @@ -12,18 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; -import cx from 'classnames'; import { css } from '@emotion/css'; +import cx from 'classnames'; +import * as React from 'react'; +import { memo } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, useStyles2 } from '@grafana/ui'; -import * as markers from './TracePageSearchBar.markers'; import UiFindInput from '../common/UiFindInput'; - import { ubFlexAuto, ubJustifyEnd } from '../uberUtilityStyles'; + +import * as markers from './TracePageSearchBar.markers'; // eslint-disable-next-line no-duplicate-imports -import { memo } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; export const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js index e7da72adffa..55fa6848dfa 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; import ListView from './index'; -import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; // Util to get list of all callbacks added to an event emitter by event type. // jest adds "error" event listeners to window, this util makes it easier to diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx index 810a6218784..57bd8f51c92 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.tsx @@ -14,9 +14,10 @@ import * as React from 'react'; -import Positions from './Positions'; import { TNil } from '../../types'; +import Positions from './Positions'; + type TWrapperProps = { style: React.CSSProperties; ref: (elm: HTMLDivElement) => void; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js index c3c85134c44..bb464b2388a 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.test.js @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { Tooltip } from '@grafana/ui'; -import ReferencesButton, { getStyles } from './ReferencesButton'; -import transformTraceData from '../model/transform-trace-data'; import traceGenerator from '../demo/trace-generators'; +import transformTraceData from '../model/transform-trace-data'; import ReferenceLink from '../url/ReferenceLink'; +import ReferencesButton, { getStyles } from './ReferencesButton'; + describe(ReferencesButton, () => { const trace = transformTraceData(traceGenerator.trace({ numberOfSpans: 10 })); const oneReference = trace.spans[1].references; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx index f48b86a243b..96456f43cf7 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { Tooltip, useStyles2 } from '@grafana/ui'; import { TraceSpanReference } from '../types/trace'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js index 70cc7b87420..70c745183f6 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.test.js @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; import { act } from 'react-dom/test-utils'; + import { Popover } from '../common/Popover'; import SpanBar from './SpanBar'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx index 097bd362bda..ed41d2c4416 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx @@ -12,18 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -import cx from 'classnames'; import { css } from '@emotion/css'; +import cx from 'classnames'; import { groupBy as _groupBy } from 'lodash'; import React, { useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; + import { autoColor } from '../Theme'; -import { TraceSpan } from '../types/trace'; +import { Popover } from '../common/Popover'; import { TNil } from '../types'; +import { TraceSpan } from '../types/trace'; + import AccordianLogs from './SpanDetail/AccordianLogs'; import { ViewedBoundsFunctionType } from './utils'; -import { Popover } from '../common/Popover'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js index 6c6c2ff78c3..104bdb2a02a 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.test.js @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { mount, shallow } from 'enzyme'; +import React from 'react'; +import ReferencesButton from './ReferencesButton'; import SpanBarRow from './SpanBarRow'; import SpanTreeOffset from './SpanTreeOffset'; -import ReferencesButton from './ReferencesButton'; jest.mock('./SpanTreeOffset', () => { // eslint-disable-next-line react/display-name diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx index 10321578de1..9239a0e1f32 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx @@ -12,26 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { css, keyframes } from '@emotion/css'; +import cx from 'classnames'; import * as React from 'react'; - import IoAlert from 'react-icons/lib/io/alert'; import IoArrowRightA from 'react-icons/lib/io/arrow-right-a'; import MdFileUpload from 'react-icons/lib/md/file-upload'; -import { css, keyframes } from '@emotion/css'; -import cx from 'classnames'; -import { Icon, stylesFactory, withTheme2 } from '@grafana/ui'; + import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, stylesFactory, withTheme2 } from '@grafana/ui'; -import ReferencesButton from './ReferencesButton'; -import TimelineRow from './TimelineRow'; -import { formatDuration, ViewedBoundsFunctionType } from './utils'; -import SpanTreeOffset from './SpanTreeOffset'; -import SpanBar from './SpanBar'; -import Ticks from './Ticks'; - +import { autoColor } from '../Theme'; import { SpanLinkFunc, TNil } from '../types'; import { TraceSpan } from '../types/trace'; -import { autoColor } from '../Theme'; + +import ReferencesButton from './ReferencesButton'; +import SpanBar from './SpanBar'; +import SpanTreeOffset from './SpanTreeOffset'; +import Ticks from './Ticks'; +import TimelineRow from './TimelineRow'; +import { formatDuration, ViewedBoundsFunctionType } from './utils'; const spanBarClassName = 'spanBar'; const spanBarLabelClassName = 'spanBarLabel'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js index 047cfd114df..c5661b11986 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import AccordianKeyValues, { KeyValuesSummary } from './AccordianKeyValues'; import * as markers from './AccordianKeyValues.markers'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx index 909b5c3f366..87ee8a16987 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { css } from '@emotion/css'; +import cx from 'classnames'; import * as React from 'react'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right'; -import { css } from '@emotion/css'; -import cx from 'classnames'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +import { autoColor } from '../../Theme'; +import { TNil } from '../../types'; +import { TraceKeyValuePair, TraceLink } from '../../types/trace'; +import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles'; import * as markers from './AccordianKeyValues.markers'; import KeyValuesTable from './KeyValuesTable'; -import { TNil } from '../../types'; -import { TraceKeyValuePair, TraceLink } from '../../types/trace'; -import { autoColor } from '../../Theme'; -import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles'; -import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; export const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js index c1d37ed98ba..3d05c9505d5 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import AccordianKeyValues from './AccordianKeyValues'; import AccordianLogs from './AccordianLogs'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx index 1a0c812dd7f..268f617802b 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; +import { css } from '@emotion/css'; import { sortBy as _sortBy } from 'lodash'; +import * as React from 'react'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right'; -import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import AccordianKeyValues from './AccordianKeyValues'; -import { formatDuration } from '../utils'; +import { autoColor } from '../../Theme'; import { TNil } from '../../types'; import { TraceLog, TraceKeyValuePair, TraceLink } from '../../types/trace'; -import { autoColor } from '../../Theme'; import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles'; +import { formatDuration } from '../utils'; + +import AccordianKeyValues from './AccordianKeyValues'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js index b69fda5e9e2..956125c6479 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.test.js @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; -import AccordianReferences, { References } from './AccordianReferences'; +import React from 'react'; + import ReferenceLink from '../../url/ReferenceLink'; +import AccordianReferences, { References } from './AccordianReferences'; + const traceID = 'trace1'; const references = [ { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx index cf5d11a36e5..7018b61692f 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx @@ -12,18 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css, cx } from '@emotion/css'; -import { Icon, useStyles2 } from '@grafana/ui'; - -import AccordianKeyValues from './AccordianKeyValues'; +import * as React from 'react'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right'; -import { TraceSpanReference } from '../../types/trace'; -import ReferenceLink from '../../url/ReferenceLink'; -import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles'; + import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; + import { autoColor } from '../../Theme'; +import { TraceSpanReference } from '../../types/trace'; +import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles'; +import ReferenceLink from '../../url/ReferenceLink'; + +import AccordianKeyValues from './AccordianKeyValues'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js index ce48a3ce162..54803bf11ba 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.test.js @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; + import AccordianText from './AccordianText'; import TextList from './TextList'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.tsx index f2bc2b74620..9af61f1375d 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianText.tsx @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; +import * as React from 'react'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right'; -import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import TextList from './TextList'; -import { TNil } from '../../types'; -import { getStyles as getAccordianKeyValuesStyles } from './AccordianKeyValues'; +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + import { autoColor } from '../../Theme'; +import { TNil } from '../../types'; import { uAlignIcon } from '../../uberUtilityStyles'; +import { getStyles as getAccordianKeyValuesStyles } from './AccordianKeyValues'; +import TextList from './TextList'; + const getStyles = (theme: GrafanaTheme2) => { return { header: css` diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js index c03b0c2bf0b..4f2ec783abd 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.test.js @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import CopyIcon from '../../common/CopyIcon'; +import { ubInlineBlock } from '../../uberUtilityStyles'; import KeyValuesTable, { LinkValue } from './KeyValuesTable'; -import { ubInlineBlock } from '../../uberUtilityStyles'; describe('LinkValue', () => { const title = 'titleValue'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index 3945e4a8b7b..9e17ff31070 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -12,17 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; -import jsonMarkup from 'json-markup'; import { css } from '@emotion/css'; import cx from 'classnames'; -import { Icon, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; +import jsonMarkup from 'json-markup'; +import * as React from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; + +import { autoColor } from '../../Theme'; import CopyIcon from '../../common/CopyIcon'; import { TNil } from '../../types'; import { TraceKeyValuePair, TraceLink } from '../../types/trace'; -import { autoColor } from '../../Theme'; import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles'; const copyIconClassName = 'copyIcon'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js index 7a9043a57f1..207e186cb77 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.test.js @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import TextList from './TextList'; describe('', () => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.tsx index 6a79a69ca67..a46685ca7e9 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/TextList.tsx @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; +import * as React from 'react'; + import { useStyles2 } from '@grafana/ui'; const getStyles = () => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js index b63b2bfcad5..f1ca85e7fd8 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.test.js @@ -14,17 +14,19 @@ jest.mock('../utils'); -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + +import LabeledList from '../../common/LabeledList'; +import traceGenerator from '../../demo/trace-generators'; +import transformTraceData from '../../model/transform-trace-data'; +import { formatDuration } from '../utils'; import AccordianKeyValues from './AccordianKeyValues'; import AccordianLogs from './AccordianLogs'; import DetailState from './DetailState'; + import SpanDetail from './index'; -import { formatDuration } from '../utils'; -import LabeledList from '../../common/LabeledList'; -import traceGenerator from '../../demo/trace-generators'; -import transformTraceData from '../../model/transform-trace-data'; describe('', () => { let wrapper; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx index 729b645e095..7f445a19f08 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx @@ -12,25 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; -import { DataLinkButton, TextArea, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, LinkModel } from '@grafana/data'; +import React from 'react'; import IoLink from 'react-icons/lib/io/link'; -import AccordianKeyValues from './AccordianKeyValues'; -import AccordianLogs from './AccordianLogs'; -import AccordianText from './AccordianText'; -import DetailState from './DetailState'; -import { formatDuration } from '../utils'; +import { GrafanaTheme2, LinkModel } from '@grafana/data'; +import { DataLinkButton, TextArea, useStyles2 } from '@grafana/ui'; + +import { autoColor } from '../../Theme'; +import { Divider } from '../../common/Divider'; import LabeledList from '../../common/LabeledList'; import { SpanLinkFunc, TNil } from '../../types'; import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan, TraceSpanReference } from '../../types/trace'; -import AccordianReferences from './AccordianReferences'; -import { autoColor } from '../../Theme'; import { uAlignIcon, ubM0, ubMb1, ubMy1, ubTxRightAlign } from '../../uberUtilityStyles'; -import { Divider } from '../../common/Divider'; +import { formatDuration } from '../utils'; + +import AccordianKeyValues from './AccordianKeyValues'; +import AccordianLogs from './AccordianLogs'; +import AccordianReferences from './AccordianReferences'; +import AccordianText from './AccordianText'; +import DetailState from './DetailState'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js index cdfe935b52d..c06156dc950 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.test.js @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { createTheme } from '@grafana/data'; -import { UnthemedSpanDetailRow } from './SpanDetailRow'; import SpanDetail from './SpanDetail'; import DetailState from './SpanDetail/DetailState'; +import { UnthemedSpanDetailRow } from './SpanDetailRow'; import SpanTreeOffset from './SpanTreeOffset'; jest.mock('./SpanTreeOffset'); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx index c82ca42087f..8414405044a 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx @@ -12,19 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2, LinkModel } from '@grafana/data'; +import { stylesFactory, withTheme2 } from '@grafana/ui'; + +import { autoColor } from '../Theme'; +import { SpanLinkFunc } from '../types'; +import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; import SpanDetail from './SpanDetail'; import DetailState from './SpanDetail/DetailState'; import SpanTreeOffset from './SpanTreeOffset'; import TimelineRow from './TimelineRow'; -import { autoColor } from '../Theme'; -import { stylesFactory, withTheme2 } from '@grafana/ui'; -import { GrafanaTheme2, LinkModel } from '@grafana/data'; - -import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; -import { SpanLinkFunc } from '../types'; const getStyles = stylesFactory((theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js index ca2539dff1b..b0c69b9570e 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.test.js @@ -16,11 +16,13 @@ import { shallow } from 'enzyme'; import React from 'react'; import IoChevronRight from 'react-icons/lib/io/chevron-right'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; + import { createTheme } from '@grafana/data'; -import SpanTreeOffset, { getStyles } from './SpanTreeOffset'; import spanAncestorIdsSpy from '../utils/span-ancestor-ids'; +import SpanTreeOffset, { getStyles } from './SpanTreeOffset'; + jest.mock('../utils/span-ancestor-ids'); describe('SpanTreeOffset', () => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx index 741dd06655f..5dbc6d8aee6 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx @@ -12,18 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; -import { get as _get } from 'lodash'; -import IoChevronRight from 'react-icons/lib/io/chevron-right'; -import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import { css } from '@emotion/css'; import cx from 'classnames'; +import { get as _get } from 'lodash'; +import React from 'react'; +import IoChevronRight from 'react-icons/lib/io/chevron-right'; +import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; + import { GrafanaTheme2 } from '@grafana/data'; import { stylesFactory, withTheme2 } from '@grafana/ui'; +import { autoColor } from '../Theme'; import { TraceSpan } from '../types/trace'; import spanAncestorIds from '../utils/span-ancestor-ids'; -import { autoColor } from '../Theme'; export const getStyles = stylesFactory((theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.test.js index 7a2bf3d267d..6c99b32c633 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import Ticks from './Ticks'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.tsx index 952b8d2b1e9..b2eace5fcff 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/Ticks.tsx @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; -import { useStyles2 } from '@grafana/ui'; +import * as React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +import { autoColor } from '../Theme'; +import { TNil } from '../types'; import { formatDuration } from './utils'; -import { TNil } from '../types'; -import { autoColor } from '../Theme'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js index 322e197cc7f..842ebd484c5 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import { TimelineCollapser } from './TimelineCollapser'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx index 5a4e407fef8..06043008504 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { IconButton, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; import React from 'react'; +import { IconButton, useStyles2 } from '@grafana/ui'; + const getStyles = () => { return { TimelineCollapser: css` diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js index 8c838a59d1c..3a3432c1fae 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.test.js @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; -import { mount } from 'enzyme'; import cx from 'classnames'; +import { mount } from 'enzyme'; +import React from 'react'; import TimelineColumnResizer, { getStyles } from './TimelineColumnResizer'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx index 72f41ea852a..0d7395d5f7b 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; +import * as React from 'react'; + import { stylesFactory } from '@grafana/ui'; import { TNil } from '../../types'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js index 95b2f5eef9e..700dfbe2df9 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.test.js @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; -import TimelineHeaderRow from './TimelineHeaderRow'; -import TimelineColumnResizer from './TimelineColumnResizer'; -import TimelineViewingLayer from './TimelineViewingLayer'; import Ticks from '../Ticks'; + import { TimelineCollapser } from './TimelineCollapser'; +import TimelineColumnResizer from './TimelineColumnResizer'; +import TimelineHeaderRow from './TimelineHeaderRow'; +import TimelineViewingLayer from './TimelineViewingLayer'; describe('', () => { let wrapper; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx index b973ec1fbd5..fb197e0c279 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; -import { useStyles2 } from '@grafana/ui'; +import * as React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +import { autoColor } from '../../Theme'; +import { ubFlex, ubPx2 } from '../../uberUtilityStyles'; +import Ticks from '../Ticks'; +import TimelineRow from '../TimelineRow'; +import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; import { TimelineCollapser } from './TimelineCollapser'; import TimelineColumnResizer from './TimelineColumnResizer'; import TimelineViewingLayer from './TimelineViewingLayer'; -import Ticks from '../Ticks'; -import TimelineRow from '../TimelineRow'; -import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; -import { autoColor } from '../../Theme'; -import { ubFlex, ubPx2 } from '../../uberUtilityStyles'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js index 43657567d40..d8c18bf163e 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.test.js @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; -import { mount } from 'enzyme'; import { cx } from '@emotion/css'; +import { mount } from 'enzyme'; +import React from 'react'; import TimelineViewingLayer, { getStyles } from './TimelineViewingLayer'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx index 7c066b8db42..d786338983e 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css, cx } from '@emotion/css'; +import * as React from 'react'; + import { stylesFactory } from '@grafana/ui'; -import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; + import { TNil } from '../../types'; import DraggableManager, { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager'; +import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; // exported for testing export const getStyles = stylesFactory(() => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineRow.tsx index 95bcd4deffe..f1d0b046874 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/TimelineRow.tsx @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; +import * as React from 'react'; + import { useStyles2 } from '@grafana/ui'; + import { ubRelative } from '../uberUtilityStyles'; const getStyles = () => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js index c47bd1bfe92..ee5186ac162 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.js @@ -11,17 +11,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow, mount } from 'enzyme'; +import React from 'react'; + +import traceGenerator from '../demo/trace-generators'; +import transformTraceData from '../model/transform-trace-data'; import ListView from './ListView'; import SpanBarRow from './SpanBarRow'; import DetailState from './SpanDetail/DetailState'; import SpanDetailRow from './SpanDetailRow'; -import VirtualizedTraceView, { DEFAULT_HEIGHTS } from './VirtualizedTraceView'; -import traceGenerator from '../demo/trace-generators'; -import transformTraceData from '../model/transform-trace-data'; import SpanTreeOffset from './SpanTreeOffset'; +import VirtualizedTraceView, { DEFAULT_HEIGHTS } from './VirtualizedTraceView'; jest.mock('./SpanTreeOffset'); diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx index b365fcc9919..4fa87f1628f 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -12,13 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; - import { isEqual } from 'lodash'; import memoizeOne from 'memoize-one'; -import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui'; +import * as React from 'react'; +import { createRef, RefObject } from 'react'; + import { GrafanaTheme2, LinkModel } from '@grafana/data'; +import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui'; + +import { Accessors } from '../ScrollManager'; +import { PEER_SERVICE } from '../constants/tag-keys'; +import { SpanLinkFunc, TNil } from '../types'; +import TTraceTimeline from '../types/TTraceTimeline'; +import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; +import { getColorByKey } from '../utils/color-generator'; import ListView from './ListView'; import SpanBarRow from './SpanBarRow'; @@ -32,13 +40,6 @@ import { spanContainsErredSpan, ViewedBoundsFunctionType, } from './utils'; -import { Accessors } from '../ScrollManager'; -import { getColorByKey } from '../utils/color-generator'; -import { SpanLinkFunc, TNil } from '../types'; -import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; -import TTraceTimeline from '../types/TTraceTimeline'; -import { PEER_SERVICE } from '../constants/tag-keys'; -import { createRef, RefObject } from 'react'; type TExtractUiFindFromStateReturn = { uiFind: string | undefined; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js index 4f85a6f1c3c..77f47586d93 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.test.js @@ -12,15 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { createTheme } from '@grafana/data'; -import TraceTimelineViewer from './index'; import traceGenerator from '../demo/trace-generators'; import transformTraceData from '../model/transform-trace-data'; + import TimelineHeaderRow from './TimelineHeaderRow'; +import TraceTimelineViewer from './index'; + describe('', () => { const trace = transformTraceData(traceGenerator.trace({})); const props = { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx index 0f1d4a1450e..7e7fd880395 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx @@ -12,21 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { RefObject } from 'react'; import { css } from '@emotion/css'; +import React, { RefObject } from 'react'; + import { GrafanaTheme2, LinkModel } from '@grafana/data'; import { stylesFactory, withTheme2 } from '@grafana/ui'; +import { Accessors } from '../ScrollManager'; +import { autoColor } from '../Theme'; +import { merge as mergeShortcuts } from '../keyboard-shortcuts'; +import { SpanLinkFunc, TNil } from '../types'; +import TTraceTimeline from '../types/TTraceTimeline'; +import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; +import ExternalLinkContext from '../url/externalLinkContext'; + import TimelineHeaderRow from './TimelineHeaderRow'; import VirtualizedTraceView from './VirtualizedTraceView'; -import { merge as mergeShortcuts } from '../keyboard-shortcuts'; -import { Accessors } from '../ScrollManager'; import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types'; -import { SpanLinkFunc, TNil } from '../types'; -import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace'; -import TTraceTimeline from '../types/TTraceTimeline'; -import { autoColor } from '../Theme'; -import ExternalLinkContext from '../url/externalLinkContext'; type TExtractUiFindFromStateReturn = { uiFind: string | undefined; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.test.js b/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.test.js index a8ad75f111f..3f6b2c0cb34 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.test.js +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.test.js @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import traceGenerator from '../demo/trace-generators'; + import { findServerChildSpan, createViewedBoundsFunc, @@ -23,8 +25,6 @@ import { formatNumber, } from './utils'; -import traceGenerator from '../demo/trace-generators'; - describe('TraceTimelineViewer/utils', () => { describe('getViewedBounds()', () => { it('works for the full range', () => { diff --git a/packages/jaeger-ui-components/src/common/BreakableText.tsx b/packages/jaeger-ui-components/src/common/BreakableText.tsx index 4e2f077046a..ad8c4cecb6d 100644 --- a/packages/jaeger-ui-components/src/common/BreakableText.tsx +++ b/packages/jaeger-ui-components/src/common/BreakableText.tsx @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; +import * as React from 'react'; + import { useStyles2 } from '@grafana/ui'; const getStyles = () => { diff --git a/packages/jaeger-ui-components/src/common/CopyIcon.test.js b/packages/jaeger-ui-components/src/common/CopyIcon.test.js index 79cfa916b8a..10ea26789c2 100644 --- a/packages/jaeger-ui-components/src/common/CopyIcon.test.js +++ b/packages/jaeger-ui-components/src/common/CopyIcon.test.js @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; -import { shallow } from 'enzyme'; import * as copy from 'copy-to-clipboard'; +import { shallow } from 'enzyme'; +import React from 'react'; + import { Button, Tooltip } from '@grafana/ui'; + import CopyIcon from './CopyIcon'; jest.mock('copy-to-clipboard'); diff --git a/packages/jaeger-ui-components/src/common/CopyIcon.tsx b/packages/jaeger-ui-components/src/common/CopyIcon.tsx index e4a91c7ff9e..6c674713045 100644 --- a/packages/jaeger-ui-components/src/common/CopyIcon.tsx +++ b/packages/jaeger-ui-components/src/common/CopyIcon.tsx @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; import copy from 'copy-to-clipboard'; +import * as React from 'react'; + import { Button, IconName, stylesFactory, Tooltip } from '@grafana/ui'; const getStyles = stylesFactory(() => { diff --git a/packages/jaeger-ui-components/src/common/Divider.tsx b/packages/jaeger-ui-components/src/common/Divider.tsx index 9977191cdd9..9eab10a61e2 100644 --- a/packages/jaeger-ui-components/src/common/Divider.tsx +++ b/packages/jaeger-ui-components/src/common/Divider.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + import { autoColor } from '../Theme'; const getStyles = (theme: GrafanaTheme2) => { diff --git a/packages/jaeger-ui-components/src/common/ExternalLinks.tsx b/packages/jaeger-ui-components/src/common/ExternalLinks.tsx index 2f82e4e7c47..96e388aff48 100644 --- a/packages/jaeger-ui-components/src/common/ExternalLinks.tsx +++ b/packages/jaeger-ui-components/src/common/ExternalLinks.tsx @@ -13,6 +13,7 @@ // limitations under the License. import * as React from 'react'; + import NewWindowIcon from './NewWindowIcon'; type Link = { diff --git a/packages/jaeger-ui-components/src/common/LabeledList.tsx b/packages/jaeger-ui-components/src/common/LabeledList.tsx index a66358dc002..7ce89630e02 100644 --- a/packages/jaeger-ui-components/src/common/LabeledList.tsx +++ b/packages/jaeger-ui-components/src/common/LabeledList.tsx @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; -import { useStyles2 } from '@grafana/ui'; +import * as React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + import { autoColor } from '../Theme'; const getStyles = (divider: boolean) => (theme: GrafanaTheme2) => { diff --git a/packages/jaeger-ui-components/src/common/NewWindowIcon.test.js b/packages/jaeger-ui-components/src/common/NewWindowIcon.test.js index d18aae61caa..160d4c2b0bd 100644 --- a/packages/jaeger-ui-components/src/common/NewWindowIcon.test.js +++ b/packages/jaeger-ui-components/src/common/NewWindowIcon.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; import NewWindowIcon, { getStyles } from './NewWindowIcon'; diff --git a/packages/jaeger-ui-components/src/common/NewWindowIcon.tsx b/packages/jaeger-ui-components/src/common/NewWindowIcon.tsx index 57256c75faf..1f5185d5df5 100644 --- a/packages/jaeger-ui-components/src/common/NewWindowIcon.tsx +++ b/packages/jaeger-ui-components/src/common/NewWindowIcon.tsx @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; -import cx from 'classnames'; -import IoAndroidOpen from 'react-icons/lib/io/android-open'; import { css } from '@emotion/css'; +import cx from 'classnames'; +import React from 'react'; +import IoAndroidOpen from 'react-icons/lib/io/android-open'; + import { useStyles2 } from '@grafana/ui'; export const getStyles = () => { diff --git a/packages/jaeger-ui-components/src/common/Popover.tsx b/packages/jaeger-ui-components/src/common/Popover.tsx index c055a3306c7..cfd1ee9e15e 100644 --- a/packages/jaeger-ui-components/src/common/Popover.tsx +++ b/packages/jaeger-ui-components/src/common/Popover.tsx @@ -1,4 +1,5 @@ import React, { ReactElement, useRef } from 'react'; + import { Popover as GrafanaPopover, PopoverController } from '@grafana/ui'; export type PopoverProps = { diff --git a/packages/jaeger-ui-components/src/common/TraceName.tsx b/packages/jaeger-ui-components/src/common/TraceName.tsx index cf13140e394..1b8af164ce4 100644 --- a/packages/jaeger-ui-components/src/common/TraceName.tsx +++ b/packages/jaeger-ui-components/src/common/TraceName.tsx @@ -12,16 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; +import * as React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import BreakableText from './BreakableText'; import { FALLBACK_TRACE_NAME } from '../constants'; import { TNil } from '../types'; +import BreakableText from './BreakableText'; + const getStyles = (theme: GrafanaTheme2) => { return { TraceName: css` diff --git a/packages/jaeger-ui-components/src/common/UiFindInput.test.js b/packages/jaeger-ui-components/src/common/UiFindInput.test.js index de5ecf72301..e918af42cb8 100644 --- a/packages/jaeger-ui-components/src/common/UiFindInput.test.js +++ b/packages/jaeger-ui-components/src/common/UiFindInput.test.js @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import { shallow } from 'enzyme'; // eslint-disable-next-line lodash/import-scope import _ from 'lodash'; +import * as React from 'react'; + import { Input } from '@grafana/ui'; import UiFindInput from './UiFindInput'; diff --git a/packages/jaeger-ui-components/src/common/UiFindInput.tsx b/packages/jaeger-ui-components/src/common/UiFindInput.tsx index f2f176c83a6..b6ca9fff3fe 100644 --- a/packages/jaeger-ui-components/src/common/UiFindInput.tsx +++ b/packages/jaeger-ui-components/src/common/UiFindInput.tsx @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { IconButton, Input } from '@grafana/ui'; import * as React from 'react'; +import { IconButton, Input } from '@grafana/ui'; + import { TNil } from '../types/index'; type Props = { diff --git a/packages/jaeger-ui-components/src/keyboard-shortcuts.tsx b/packages/jaeger-ui-components/src/keyboard-shortcuts.tsx index 92d2243615e..692935f7940 100644 --- a/packages/jaeger-ui-components/src/keyboard-shortcuts.tsx +++ b/packages/jaeger-ui-components/src/keyboard-shortcuts.tsx @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; import Combokeys from 'combokeys'; +import * as React from 'react'; import keyboardMappings from './keyboard-mappings'; diff --git a/packages/jaeger-ui-components/src/model/link-patterns.tsx b/packages/jaeger-ui-components/src/model/link-patterns.tsx index ee750905464..6031ddfdb1c 100644 --- a/packages/jaeger-ui-components/src/model/link-patterns.tsx +++ b/packages/jaeger-ui-components/src/model/link-patterns.tsx @@ -14,10 +14,12 @@ import { uniq as _uniq } from 'lodash'; import memoize from 'lru-memoize'; -import { getConfigValue } from '../utils/config/get-config'; -import { getParent } from './span'; + import { TNil } from '../types'; import { TraceSpan, TraceLink, TraceKeyValuePair, Trace } from '../types/trace'; +import { getConfigValue } from '../utils/config/get-config'; + +import { getParent } from './span'; const parameterRegExp = /#\{([^{}]*)\}/g; diff --git a/packages/jaeger-ui-components/src/model/transform-trace-data.tsx b/packages/jaeger-ui-components/src/model/transform-trace-data.tsx index 1174a2e260b..18fe5917311 100644 --- a/packages/jaeger-ui-components/src/model/transform-trace-data.tsx +++ b/packages/jaeger-ui-components/src/model/transform-trace-data.tsx @@ -16,10 +16,11 @@ import { isEqual as _isEqual } from 'lodash'; // @ts-ignore import { getTraceSpanIdsAsTree } from '../selectors/trace'; -import { getConfigValue } from '../utils/config/get-config'; import { TraceKeyValuePair, TraceSpan, Trace, TraceResponse } from '../types/trace'; // @ts-ignore import TreeNode from '../utils/TreeNode'; +import { getConfigValue } from '../utils/config/get-config'; + import { getTraceName } from './trace-viewer'; // exported for tests diff --git a/packages/jaeger-ui-components/src/scroll-page.test.js b/packages/jaeger-ui-components/src/scroll-page.test.js index 3b60f1d0bed..a697fd9ce4f 100644 --- a/packages/jaeger-ui-components/src/scroll-page.test.js +++ b/packages/jaeger-ui-components/src/scroll-page.test.js @@ -14,8 +14,8 @@ jest.mock('./Tween'); -import { scrollBy, scrollTo, cancel } from './scroll-page'; import Tween from './Tween'; +import { scrollBy, scrollTo, cancel } from './scroll-page'; // keep track of instances, manually // https://github.com/facebook/jest/issues/5019 diff --git a/packages/jaeger-ui-components/src/selectors/process.test.js b/packages/jaeger-ui-components/src/selectors/process.test.js index 0326198fc97..9edc171fd23 100644 --- a/packages/jaeger-ui-components/src/selectors/process.test.js +++ b/packages/jaeger-ui-components/src/selectors/process.test.js @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as processSelectors from './process'; import traceGenerator from '../demo/trace-generators'; +import * as processSelectors from './process'; + const generatedTrace = traceGenerator.trace({ numberOfSpans: 45 }); it('getProcessServiceName() should return the serviceName of the process', () => { diff --git a/packages/jaeger-ui-components/src/selectors/span.js b/packages/jaeger-ui-components/src/selectors/span.js index f528e65a182..cd0a827dc4c 100644 --- a/packages/jaeger-ui-components/src/selectors/span.js +++ b/packages/jaeger-ui-components/src/selectors/span.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { createSelector } from 'reselect'; import fuzzy from 'fuzzy'; +import { createSelector } from 'reselect'; import { getProcessServiceName } from './process'; diff --git a/packages/jaeger-ui-components/src/selectors/span.test.js b/packages/jaeger-ui-components/src/selectors/span.test.js index 11e2e28c388..12893f81628 100644 --- a/packages/jaeger-ui-components/src/selectors/span.test.js +++ b/packages/jaeger-ui-components/src/selectors/span.test.js @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as spanSelectors from './span'; import traceGenerator from '../demo/trace-generators'; +import * as spanSelectors from './span'; + const generatedTrace = traceGenerator.trace({ numberOfSpans: 45 }); it('getSpanId() should return the name of the span', () => { diff --git a/packages/jaeger-ui-components/src/selectors/trace.js b/packages/jaeger-ui-components/src/selectors/trace.js index ea93313960e..35776aef0df 100644 --- a/packages/jaeger-ui-components/src/selectors/trace.js +++ b/packages/jaeger-ui-components/src/selectors/trace.js @@ -14,6 +14,11 @@ import { createSelector, createStructuredSelector } from 'reselect'; +import TreeNode from '../utils/TreeNode'; +import { formatMillisecondTime, formatSecondTime, ONE_SECOND } from '../utils/date'; +import { numberSortComparator } from '../utils/sort'; + +import { getProcessServiceName } from './process'; import { getSpanId, getSpanName, @@ -22,10 +27,6 @@ import { getSpanDuration, getSpanProcessId, } from './span'; -import { getProcessServiceName } from './process'; -import { formatMillisecondTime, formatSecondTime, ONE_SECOND } from '../utils/date'; -import { numberSortComparator } from '../utils/sort'; -import TreeNode from '../utils/TreeNode'; export const getTraceId = (trace) => trace.traceID; diff --git a/packages/jaeger-ui-components/src/selectors/trace.test.js b/packages/jaeger-ui-components/src/selectors/trace.test.js index 234130c40f4..3e20f4d7060 100644 --- a/packages/jaeger-ui-components/src/selectors/trace.test.js +++ b/packages/jaeger-ui-components/src/selectors/trace.test.js @@ -14,7 +14,9 @@ import { values as _values } from 'lodash'; -import { followsFromRef } from './trace.fixture'; +import traceGenerator from '../demo/trace-generators'; +import { numberSortComparator } from '../utils/sort'; + import { getSpanId, getSpanName, @@ -25,8 +27,7 @@ import { getSpanTimestamp, } from './span'; import * as traceSelectors from './trace'; -import traceGenerator from '../demo/trace-generators'; -import { numberSortComparator } from '../utils/sort'; +import { followsFromRef } from './trace.fixture'; const generatedTrace = traceGenerator.trace({ numberOfSpans: 45 }); diff --git a/packages/jaeger-ui-components/src/types/TDdgState.tsx b/packages/jaeger-ui-components/src/types/TDdgState.tsx index 4540714a790..f1aba58ac7c 100644 --- a/packages/jaeger-ui-components/src/types/TDdgState.tsx +++ b/packages/jaeger-ui-components/src/types/TDdgState.tsx @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ApiError } from './api-error'; import { fetchedState } from '../constants'; import { TDdgModel } from '../model/ddg/types'; +import { ApiError } from './api-error'; + export type TDdgStateEntry = | { state: typeof fetchedState.LOADING; diff --git a/packages/jaeger-ui-components/src/types/TTraceTimeline.tsx b/packages/jaeger-ui-components/src/types/TTraceTimeline.tsx index 1fd1f4300b5..2441cc05af5 100644 --- a/packages/jaeger-ui-components/src/types/TTraceTimeline.tsx +++ b/packages/jaeger-ui-components/src/types/TTraceTimeline.tsx @@ -13,6 +13,7 @@ // limitations under the License. import DetailState from '../TraceTimelineViewer/SpanDetail/DetailState'; + import TNil from './TNil'; type TTraceTimeline = { diff --git a/packages/jaeger-ui-components/src/types/links.ts b/packages/jaeger-ui-components/src/types/links.ts index 58448dcb302..10d7d9dc4f5 100644 --- a/packages/jaeger-ui-components/src/types/links.ts +++ b/packages/jaeger-ui-components/src/types/links.ts @@ -1,6 +1,7 @@ -import { TraceSpan } from './trace'; import React from 'react'; +import { TraceSpan } from './trace'; + export type SpanLinkDef = { href: string; onClick?: (event: any) => void; diff --git a/packages/jaeger-ui-components/src/url/ReferenceLink.test.js b/packages/jaeger-ui-components/src/url/ReferenceLink.test.js index 63522aaeb77..1334f8b980c 100644 --- a/packages/jaeger-ui-components/src/url/ReferenceLink.test.js +++ b/packages/jaeger-ui-components/src/url/ReferenceLink.test.js @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React from 'react'; import { shallow, mount } from 'enzyme'; +import React from 'react'; import ReferenceLink from './ReferenceLink'; import ExternalLinkContext from './externalLinkContext'; diff --git a/packages/jaeger-ui-components/src/url/ReferenceLink.tsx b/packages/jaeger-ui-components/src/url/ReferenceLink.tsx index 631cd86e2b1..9841e9874df 100644 --- a/packages/jaeger-ui-components/src/url/ReferenceLink.tsx +++ b/packages/jaeger-ui-components/src/url/ReferenceLink.tsx @@ -13,7 +13,9 @@ // limitations under the License. import React from 'react'; + import { TraceSpanReference } from '../types/trace'; + import ExternalLinkContext from './externalLinkContext'; type ReferenceLinkProps = { diff --git a/packages/jaeger-ui-components/src/utils/DraggableManager/DraggableManager.tsx b/packages/jaeger-ui-components/src/utils/DraggableManager/DraggableManager.tsx index a1401dc19d2..f338288798e 100644 --- a/packages/jaeger-ui-components/src/utils/DraggableManager/DraggableManager.tsx +++ b/packages/jaeger-ui-components/src/utils/DraggableManager/DraggableManager.tsx @@ -14,9 +14,10 @@ import { get as _get } from 'lodash'; +import { TNil } from '../../types'; + import EUpdateTypes from './EUpdateTypes'; import { DraggableBounds, DraggingUpdate } from './types'; -import { TNil } from '../../types'; const LEFT_MOUSE_BUTTON = 0; diff --git a/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DividerDemo.tsx b/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DividerDemo.tsx index e1a73acc297..ca8ce693956 100644 --- a/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DividerDemo.tsx +++ b/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DividerDemo.tsx @@ -15,8 +15,8 @@ import React from 'react'; import { DraggableBounds, DraggingUpdate } from '..'; -import DraggableManager from '../DraggableManager'; import TNil from '../../../types/TNil'; +import DraggableManager from '../DraggableManager'; import './DividerDemo.css'; diff --git a/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DraggableManagerDemo.tsx b/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DraggableManagerDemo.tsx index 0cf45ae6b94..439a7b5c7e2 100644 --- a/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DraggableManagerDemo.tsx +++ b/packages/jaeger-ui-components/src/utils/DraggableManager/demo/DraggableManagerDemo.tsx @@ -14,9 +14,10 @@ import React from 'react'; +import { TNil } from '../../../types'; + import DividerDemo from './DividerDemo'; import RegionDemo from './RegionDemo'; -import { TNil } from '../../../types'; import './DraggableManagerDemo.css'; diff --git a/packages/jaeger-ui-components/src/utils/DraggableManager/types.tsx b/packages/jaeger-ui-components/src/utils/DraggableManager/types.tsx index 16ab7948d4e..b10c4581199 100644 --- a/packages/jaeger-ui-components/src/utils/DraggableManager/types.tsx +++ b/packages/jaeger-ui-components/src/utils/DraggableManager/types.tsx @@ -14,9 +14,10 @@ import * as React from 'react'; +import { TNil } from '../../types'; + import DraggableManager from './DraggableManager'; import EUpdateTypes from './EUpdateTypes'; -import { TNil } from '../../types'; export type DraggableBounds = { clientXLeft: number; diff --git a/packages/jaeger-ui-components/src/utils/color-generator.test.js b/packages/jaeger-ui-components/src/utils/color-generator.test.js index d211220bbb1..894dd69ab81 100644 --- a/packages/jaeger-ui-components/src/utils/color-generator.test.js +++ b/packages/jaeger-ui-components/src/utils/color-generator.test.js @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { getColorByKey, clear } from './color-generator'; import { createTheme } from '@grafana/data'; +import { getColorByKey, clear } from './color-generator'; + it('gives the same color for the same key', () => { clear(); const colorOne = getColorByKey('serviceA', createTheme()); diff --git a/packages/jaeger-ui-components/src/utils/color-generator.tsx b/packages/jaeger-ui-components/src/utils/color-generator.tsx index c4d7ef614d9..aa5bc87dfd6 100644 --- a/packages/jaeger-ui-components/src/utils/color-generator.tsx +++ b/packages/jaeger-ui-components/src/utils/color-generator.tsx @@ -13,6 +13,7 @@ // limitations under the License. import memoizeOne from 'memoize-one'; + import { GrafanaTheme2 } from '@grafana/data'; import { colors } from '@grafana/ui'; diff --git a/packages/jaeger-ui-components/src/utils/date.tsx b/packages/jaeger-ui-components/src/utils/date.tsx index eb5aa6a795a..1a0277f189f 100644 --- a/packages/jaeger-ui-components/src/utils/date.tsx +++ b/packages/jaeger-ui-components/src/utils/date.tsx @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import moment from 'moment-timezone'; import { round as _round, dropWhile as _dropWhile } from 'lodash'; +import moment from 'moment-timezone'; import { toFloatPrecision } from './number'; diff --git a/packages/jaeger-ui-components/src/utils/filter-spans.tsx b/packages/jaeger-ui-components/src/utils/filter-spans.tsx index 6647201bf37..699d8996487 100644 --- a/packages/jaeger-ui-components/src/utils/filter-spans.tsx +++ b/packages/jaeger-ui-components/src/utils/filter-spans.tsx @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceKeyValuePair, TraceSpan } from '../types/trace'; import { TNil } from '../types'; +import { TraceKeyValuePair, TraceSpan } from '../types/trace'; export default function filterSpans(textFilter: string, spans: TraceSpan[] | TNil) { if (!spans) { diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index e1c3eed6acd..9f948866810 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -174,19 +174,15 @@ func (hs *HTTPServer) declareFixedRoles() error { DisplayName: "APIKeys writer", Description: "Gives access to add and delete api keys.", Group: "API Keys", - Permissions: []ac.Permission{ + Permissions: ac.ConcatPermissions(apikeyReaderRole.Role.Permissions, []ac.Permission{ { Action: ac.ActionAPIKeyCreate, }, - { - Action: ac.ActionAPIKeyRead, - Scope: ac.ScopeAPIKeysAll, - }, { Action: ac.ActionAPIKeyDelete, Scope: ac.ScopeAPIKeysAll, }, - }, + }), }, Grants: []string{string(models.ROLE_ADMIN)}, } diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 195c931fe76..7aa1a2a3a2e 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/annotations" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -64,22 +63,7 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } - var canSave bool - var err error - if cmd.DashboardId != 0 { - canSave, err = canSaveDashboardAnnotation(c, cmd.DashboardId) - } else { // organization annotations - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { - canSave = canSaveOrganizationAnnotation(c) - } else { - // This is an additional validation needed only for FGAC Organization Annotations. - // It is not possible to do it in the middleware because we need to look - // into the request to determine if this is a Organization annotation or not - canSave, err = hs.canCreateOrganizationAnnotation(c) - } - } - - if err != nil || !canSave { + if canSave, err := hs.canCreateAnnotation(c, cmd.DashboardId); err != nil || !canSave { return dashboardGuardianResponse(err) } @@ -198,16 +182,7 @@ func (hs *HTTPServer) UpdateAnnotation(c *models.ReqContext) response.Response { return resp } - canSave := true - if annotation.GetType() == annotations.Dashboard { - canSave, err = canSaveDashboardAnnotation(c, annotation.DashboardId) - } else { - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { - canSave = canSaveOrganizationAnnotation(c) - } - } - - if err != nil || !canSave { + if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave { return dashboardGuardianResponse(err) } @@ -245,15 +220,7 @@ func (hs *HTTPServer) PatchAnnotation(c *models.ReqContext) response.Response { return resp } - canSave := true - if annotation.GetType() == annotations.Dashboard { - canSave, err = canSaveDashboardAnnotation(c, annotation.DashboardId) - } else { - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { - canSave = canSaveOrganizationAnnotation(c) - } - } - if err != nil || !canSave { + if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave { return dashboardGuardianResponse(err) } @@ -305,9 +272,9 @@ func (hs *HTTPServer) MassDeleteAnnotations(c *models.ReqContext) response.Respo repo := annotations.GetRepository() var deleteParams *annotations.DeleteParams - // validations only for FGAC. A user can mass delete all annotations in a (dashboard + panel) or a specific annotation + // validations only for RBAC. A user can mass delete all annotations in a (dashboard + panel) or a specific annotation // if has access to that dashboard. - if hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !hs.AccessControl.IsDisabled() { var dashboardId int64 if cmd.AnnotationId != 0 { @@ -364,16 +331,7 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon return resp } - canSave := true - if annotation.GetType() == annotations.Dashboard { - canSave, err = canSaveDashboardAnnotation(c, annotation.DashboardId) - } else { - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { - canSave = canSaveOrganizationAnnotation(c) - } - } - - if err != nil || !canSave { + if canSave, err := hs.canSaveAnnotation(c, annotation); err != nil || !canSave { return dashboardGuardianResponse(err) } @@ -388,7 +346,18 @@ func (hs *HTTPServer) DeleteAnnotationByID(c *models.ReqContext) response.Respon return response.Success("Annotation deleted") } -func canSaveDashboardAnnotation(c *models.ReqContext, dashboardID int64) (bool, error) { +func (hs *HTTPServer) canSaveAnnotation(c *models.ReqContext, annotation *annotations.ItemDTO) (bool, error) { + if annotation.GetType() == annotations.Dashboard { + return canEditDashboard(c, annotation.DashboardId) + } else { + if hs.AccessControl.IsDisabled() { + return c.SignedInUser.HasRole(models.ROLE_EDITOR), nil + } + return true, nil + } +} + +func canEditDashboard(c *models.ReqContext, dashboardID int64) (bool, error) { guard := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser) if canEdit, err := guard.CanEdit(); err != nil || !canEdit { return false, err @@ -397,10 +366,6 @@ func canSaveDashboardAnnotation(c *models.ReqContext, dashboardID int64) (bool, return true, nil } -func canSaveOrganizationAnnotation(c *models.ReqContext) bool { - return c.SignedInUser.HasRole(models.ROLE_EDITOR) -} - func findAnnotationByID(ctx context.Context, repo annotations.Repository, annotationID int64, user *models.SignedInUser) (*annotations.ItemDTO, response.Response) { query := &annotations.ItemQuery{ AnnotationId: annotationID, @@ -478,9 +443,23 @@ func AnnotationTypeScopeResolver() (string, accesscontrol.AttributeScopeResolveF return accesscontrol.ScopeAnnotationsProvider.GetResourceScope(""), annotationTypeResolver } -func (hs *HTTPServer) canCreateOrganizationAnnotation(c *models.ReqContext) (bool, error) { - evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeOrganization) - return hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator) +func (hs *HTTPServer) canCreateAnnotation(c *models.ReqContext, dashboardId int64) (bool, error) { + if dashboardId != 0 { + if !hs.AccessControl.IsDisabled() { + evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeDashboard) + if canSave, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator); err != nil || !canSave { + return canSave, err + } + } + return canEditDashboard(c, dashboardId) + } else { // organization annotations + if !hs.AccessControl.IsDisabled() { + evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsCreate, accesscontrol.ScopeAnnotationsTypeOrganization) + return hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluator) + } else { + return c.SignedInUser.HasRole(models.ROLE_EDITOR), nil + } + } } func (hs *HTTPServer) canMassDeleteAnnotations(c *models.ReqContext, dashboardID int64) (bool, error) { @@ -494,7 +473,7 @@ func (hs *HTTPServer) canMassDeleteAnnotations(c *models.ReqContext, dashboardID return false, err } - canSave, err = canSaveDashboardAnnotation(c, dashboardID) + canSave, err = canEditDashboard(c, dashboardID) if err != nil || !canSave { return false, err } diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 300a751454a..2b0aff9d083 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -615,6 +615,18 @@ func TestAPI_Annotations_AccessControl(t *testing.T) { }, want: http.StatusForbidden, }, + { + name: "AccessControl create dashboard annotation with incorrect permissions is forbidden", + args: args{ + permissions: []*accesscontrol.Permission{{ + Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization, + }}, + url: "/api/annotations", + method: http.MethodPost, + body: mockRequestBody(postDashboardCmd), + }, + want: http.StatusForbidden, + }, { name: "AccessControl create organization annotation with permissions is allowed", args: args{ @@ -708,7 +720,7 @@ func TestAPI_Annotations_AccessControl(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - setUpFGACGuardian(t) + setUpRBACGuardian(t) sc.acmock. RegisterAttributeScopeResolver(AnnotationTypeScopeResolver()) setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgId) @@ -902,7 +914,7 @@ func TestAPI_MassDeleteAnnotations_AccessControl(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - setUpFGACGuardian(t) + setUpRBACGuardian(t) setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgId) dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1} organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} @@ -933,7 +945,7 @@ func setUpACL() { guardian.InitLegacyGuardian(store) } -func setUpFGACGuardian(t *testing.T) { +func setUpRBACGuardian(t *testing.T) { origNewGuardian := guardian.New t.Cleanup(func() { guardian.New = origNewGuardian diff --git a/pkg/api/api.go b/pkg/api/api.go index a7de6111fab..24883640e05 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -354,6 +354,12 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Group("/dashboards", func(dashboardRoute routing.RouteRegister) { dashboardRoute.Get("/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsRead)), routing.Wrap(hs.GetDashboard)) dashboardRoute.Delete("/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsDelete)), routing.Wrap(hs.DeleteDashboardByUID)) + dashboardRoute.Group("/uid/:uid", func(dashUidRoute routing.RouteRegister) { + dashUidRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { + dashboardPermissionRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsPermissionsRead)), routing.Wrap(hs.GetDashboardPermissionList)) + dashboardPermissionRoute.Post("/", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsPermissionsWrite)), routing.Wrap(hs.UpdateDashboardPermissions)) + }) + }) if hs.ThumbService != nil { dashboardRoute.Get("/uid/:uid/img/:kind/:theme", hs.ThumbService.GetImage) @@ -371,6 +377,7 @@ func (hs *HTTPServer) registerRoutes() { dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard)) dashboardRoute.Get("/tags", hs.GetDashboardTags) + // Deprecated: use /uid/:uid API instead. dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) { dashIdRoute.Get("/versions", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersions)) dashIdRoute.Get("/versions/:id", authorize(reqSignedIn, ac.EvalPermission(ac.ActionDashboardsWrite)), routing.Wrap(hs.GetDashboardVersion)) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 18986610b82..84b913a0437 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -34,6 +34,7 @@ import ( "github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/login/loginservice" "github.com/grafana/grafana/pkg/services/login/logintest" + "github.com/grafana/grafana/pkg/services/preference/preftest" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/searchusers" @@ -381,6 +382,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo SQLStore: store, searchUsersService: searchusers.ProvideUsersService(db, filters.ProvideOSSSearchUserFilter()), dashboardService: dashboardservice.ProvideDashboardService(cfg, dashboardsStore, nil, features, accesscontrolmock.NewPermissionsServicesMock()), + preferenceService: preftest.NewPreferenceServiceFake(), } // Defining the accesscontrol service has to be done before registering routes diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index ec047b03590..299d8f3d9e6 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/store" + pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -432,20 +433,21 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa // GetHomeDashboard returns the home dashboard. func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response { - prefsQuery := models.GetPreferencesWithDefaultsQuery{User: c.SignedInUser} + prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: c.SignedInUser.UserId} homePage := hs.Cfg.HomePage - if err := hs.SQLStore.GetPreferencesWithDefaults(c.Req.Context(), &prefsQuery); err != nil { + preference, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) + if err != nil { return response.Error(500, "Failed to get preferences", err) } - if prefsQuery.Result.HomeDashboardId == 0 && len(homePage) > 0 { + if preference.HomeDashboardID == 0 && len(homePage) > 0 { homePageRedirect := dtos.DashboardRedirect{RedirectUri: homePage} return response.JSON(http.StatusOK, &homePageRedirect) } - if prefsQuery.Result.HomeDashboardId != 0 { - slugQuery := models.GetDashboardRefByIdQuery{Id: prefsQuery.Result.HomeDashboardId} + if preference.HomeDashboardID != 0 { + slugQuery := models.GetDashboardRefByIdQuery{Id: preference.HomeDashboardID} err := hs.SQLStore.GetDashboardUIDById(c.Req.Context(), &slugQuery) if err == nil { url := models.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug) diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go index 0415fe0262e..184c7c53c58 100644 --- a/pkg/api/dashboard_permission.go +++ b/pkg/api/dashboard_permission.go @@ -11,22 +11,30 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/web" ) func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response.Response { - dashID, err := strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + var dashID int64 + var err error + dashUID := web.Params(c.Req)[":uid"] + if dashUID == "" { + dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + } } - _, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, "") + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, dashUID) if rsp != nil { return rsp } + if dashID == 0 { + dashID = dash.Id + } + g := guardian.New(c.Req.Context(), dashID, c.OrgId, c.SignedInUser) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { @@ -60,6 +68,8 @@ func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response. } func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response.Response { + var dashID int64 + var err error apiCmd := dtos.UpdateDashboardAclCommand{} if err := web.Bind(c.Req, &apiCmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) @@ -68,16 +78,23 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response. return response.Error(400, err.Error(), err) } - dashID, err := strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) - if err != nil { - return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + dashUID := web.Params(c.Req)[":uid"] + if dashUID == "" { + dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) + } } - dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, dashID, "") + dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.OrgId, 0, dashUID) if rsp != nil { return rsp } + if dashUID != "" { + dashID = dash.Id + } + g := guardian.New(c.Req.Context(), dashID, c.OrgId, c.SignedInUser) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { return dashboardGuardianResponse(err) @@ -115,7 +132,7 @@ func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response. return response.Error(403, "Cannot remove own admin permission for a folder", nil) } - if hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !hs.AccessControl.IsDisabled() { old, err := g.GetAcl() if err != nil { return response.Error(500, "Error while checking dashboard permissions", err) diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index 18c9a90e0ca..136bb08e8a8 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -38,6 +38,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { dashboardService: dashboardservice.ProvideDashboardService( settings, dashboardStore, nil, features, accesscontrolmock.NewPermissionsServicesMock(), ), + AccessControl: accesscontrolmock.New().WithDisabled(), } t.Run("Given user has no admin permissions", func(t *testing.T) { diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 0a3735e284a..a0f7f4fbc34 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -28,6 +28,8 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/live" + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/services/preference/preftest" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -43,11 +45,13 @@ func TestGetHomeDashboard(t *testing.T) { req := &models.ReqContext{SignedInUser: &models.SignedInUser{}, Context: &web.Context{Req: httpReq}} cfg := setting.NewCfg() cfg.StaticRootPath = "../../public/" + prefService := preftest.NewPreferenceServiceFake() hs := &HTTPServer{ - Cfg: cfg, - pluginStore: &fakePluginStore{}, - SQLStore: mockstore.NewSQLStoreMock(), + Cfg: cfg, + pluginStore: &fakePluginStore{}, + SQLStore: mockstore.NewSQLStoreMock(), + preferenceService: prefService, } tests := []struct { @@ -71,6 +75,8 @@ func TestGetHomeDashboard(t *testing.T) { bytes, err := simplejson.NewJson(homeDashJSON) require.NoError(t, err, "must be able to encode file as JSON") + prefService.ExpectedPreference = &pref.Preference{} + dash.Dashboard = bytes b, err := json.Marshal(dash) diff --git a/pkg/api/docs/definitions/dashboard.go b/pkg/api/docs/definitions/dashboard.go index 1c261a7da95..a115763cf25 100644 --- a/pkg/api/docs/definitions/dashboard.go +++ b/pkg/api/docs/definitions/dashboard.go @@ -101,7 +101,7 @@ import ( // 422: unprocessableEntityError // 500: internalServerError -// swagger:parameters getDashboardByUID deleteDashboardByUID +// swagger:parameters getDashboardByUID deleteDashboardByUID getDashboardPermissionsWithUid postDashboardPermissionsWithUid type UID struct { // in:path // required:true diff --git a/pkg/api/docs/definitions/dashboard_permissions.go b/pkg/api/docs/definitions/dashboard_permissions.go index 940638dfd1f..46ae3e96865 100644 --- a/pkg/api/docs/definitions/dashboard_permissions.go +++ b/pkg/api/docs/definitions/dashboard_permissions.go @@ -9,6 +9,10 @@ import ( // // Gets all existing permissions for the given dashboard. // +// Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsWithUid) instead +// +// Deprecated: true +// // Responses: // 200: getDashboardPermissionsResponse // 401: unauthorisedError @@ -20,6 +24,35 @@ import ( // // Updates permissions for a dashboard. // +// Please refer to [updated API](#/dashboard_permissions/postDashboardPermissionsWithUid) instead +// +// This operation will remove existing permissions if they’re not included in the request. +// +// Deprecated: true +// +// Responses: +// 200: okResponse +// 400: badRequestError +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + +// swagger:route GET /dashboards/uid/{uid}/permissions dashboard_permissions getDashboardPermissionsWithUid +// +// Gets all existing permissions for the given dashboard. +// +// Responses: +// 200: getDashboardPermissionsResponse +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + +// swagger:route POST /dashboards/uid/{uid}/permissions dashboard_permissions postDashboardPermissionsWithUid +// +// Updates permissions for a dashboard. +// // This operation will remove existing permissions if they’re not included in the request. // // Responses: @@ -30,7 +63,7 @@ import ( // 404: notFoundError // 500: internalServerError -// swagger:parameters postDashboardPermissions updateFolderPermissions +// swagger:parameters postDashboardPermissions updateFolderPermissions postDashboardPermissionsWithUid type PostDashboardPermissionsParam struct { // in:body // required:true diff --git a/pkg/api/dtos/prefs.go b/pkg/api/dtos/prefs.go index e8f929e6da4..08ef64b6df5 100644 --- a/pkg/api/dtos/prefs.go +++ b/pkg/api/dtos/prefs.go @@ -1,14 +1,17 @@ package dtos -import "github.com/grafana/grafana/pkg/models" +import ( + "github.com/grafana/grafana/pkg/models" + pref "github.com/grafana/grafana/pkg/services/preference" +) type Prefs struct { - Theme string `json:"theme"` - HomeDashboardID int64 `json:"homeDashboardId"` - Timezone string `json:"timezone"` - WeekStart string `json:"weekStart"` - Navbar models.NavbarPreference `json:"navbar,omitempty"` - QueryHistory models.QueryHistoryPreference `json:"queryHistory,omitempty"` + Theme string `json:"theme"` + HomeDashboardID int64 `json:"homeDashboardId"` + Timezone string `json:"timezone"` + WeekStart string `json:"weekStart"` + Navbar pref.NavbarPreference `json:"navbar,omitempty"` + QueryHistory pref.QueryHistoryPreference `json:"queryHistory,omitempty"` } // swagger:model @@ -33,8 +36,8 @@ type PatchPrefsCmd struct { // Default:0 HomeDashboardID *int64 `json:"homeDashboardId,omitempty"` // Enum: utc,browser - Timezone *string `json:"timezone,omitempty"` - WeekStart *string `json:"weekStart,omitempty"` - Navbar *models.NavbarPreference `json:"navbar,omitempty"` - QueryHistory *models.QueryHistoryPreference `json:"queryHistory,omitempty"` + Timezone *string `json:"timezone,omitempty"` + WeekStart *string `json:"weekStart,omitempty"` + Navbar *pref.NavbarPreference `json:"navbar,omitempty"` + QueryHistory *pref.QueryHistoryPreference `json:"queryHistory,omitempty"` } diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 3a14be06d33..9efde7251f9 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -52,7 +52,7 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response { if err != nil { return response.Error(http.StatusBadRequest, "id is invalid", err) } - folder, err := hs.folderService.GetFolderByID(c.Req.Context(), c.SignedInUser, c.OrgId, id) + folder, err := hs.folderService.GetFolderByID(c.Req.Context(), c.SignedInUser, id, c.OrgId) if err != nil { return apierrors.ToFolderErrorResponse(err) } diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index 830c71b7afd..edaff924528 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -115,7 +114,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res return response.Error(403, "Cannot remove own admin permission for a folder", nil) } - if hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !hs.AccessControl.IsDisabled() { old, err := g.GetAcl() if err != nil { return response.Error(500, "Error while checking dashboard permissions", err) diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index 9198f15e55b..24556b120d7 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -44,6 +44,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { dashboardService: service.ProvideDashboardService( settings, dashboardStore, nil, features, permissionsServices, ), + AccessControl: accesscontrolmock.New().WithDisabled(), } t.Run("Given folder not exists", func(t *testing.T) { diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index cf85567b308..59478493323 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -51,6 +51,7 @@ import ( "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/plugindashboards" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service" + pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/queryhistory" @@ -147,6 +148,7 @@ type HTTPServer struct { DashboardsnapshotsService *dashboardsnapshots.Service PluginSettings *pluginSettings.Service AvatarCacheServer *avatar.AvatarCacheServer + preferenceService pref.Service entityEventsService store.EntityEventsService } @@ -179,7 +181,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi dashboardProvisioningService dashboards.DashboardProvisioningService, folderService dashboards.FolderService, datasourcePermissionsService permissions.DatasourcePermissionsService, alertNotificationService *alerting.AlertNotificationService, dashboardsnapshotsService *dashboardsnapshots.Service, commentsService *comments.Service, pluginSettings *pluginSettings.Service, - avatarCacheServer *avatar.AvatarCacheServer, entityEventsService store.EntityEventsService, + avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service, entityEventsService store.EntityEventsService, ) (*HTTPServer, error) { web.Env = cfg.Env m := web.New() @@ -252,6 +254,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi PluginSettings: pluginSettings, permissionServices: permissionsServices, AvatarCacheServer: avatarCacheServer, + preferenceService: preferenceService, entityEventsService: entityEventsService, } if hs.Listener != nil { diff --git a/pkg/api/index.go b/pkg/api/index.go index d7d8f5e133a..42c0c18948c 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/setting" ) @@ -38,6 +39,12 @@ func (hs *HTTPServer) getProfileNode(c *models.ReqContext) *dtos.NavLink { }, } + if hs.Features.IsEnabled(featuremgmt.FlagPersistNotifications) { + children = append(children, &dtos.NavLink{ + Text: "Notifications", Id: "notifications", Url: hs.Cfg.AppSubURL + "/notifications", Icon: "bell", + }) + } + if setting.AddChangePasswordLink() { children = append(children, &dtos.NavLink{ Text: "Change password", Id: "change-password", Url: hs.Cfg.AppSubURL + "/profile/password", @@ -161,12 +168,12 @@ func (hs *HTTPServer) ReqCanAdminTeams(c *models.ReqContext) bool { return c.OrgRole == models.ROLE_ADMIN || (hs.Cfg.EditorsCanAdmin && c.OrgRole == models.ROLE_EDITOR) } -func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dtos.NavLink, error) { +func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool, prefs *pref.Preference) ([]*dtos.NavLink, error) { hasAccess := ac.HasAccess(hs.AccessControl, c) navTree := []*dtos.NavLink{} if hs.Features.IsEnabled(featuremgmt.FlagNewNavigation) { - savedItemsLinks, err := hs.buildSavedItemsNavLinks(c) + savedItemsLinks, err := hs.buildSavedItemsNavLinks(c, prefs) if err != nil { return nil, err } @@ -233,8 +240,10 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto _, uaIsDisabledForOrg := hs.Cfg.UnifiedAlerting.DisabledOrgs[c.OrgId] uaVisibleForOrg := hs.Cfg.UnifiedAlerting.IsEnabled() && !uaIsDisabledForOrg - if setting.AlertingEnabled != nil && *setting.AlertingEnabled || uaVisibleForOrg { - navTree = append(navTree, hs.buildAlertNavLinks(c, uaVisibleForOrg, hasEditPerm)...) + if setting.AlertingEnabled != nil && *setting.AlertingEnabled { + navTree = append(navTree, hs.buildLegacyAlertNavLinks()...) + } else if uaVisibleForOrg { + navTree = append(navTree, hs.buildAlertNavLinks(c)...) } appLinks, err := hs.getAppLinks(c) @@ -402,20 +411,16 @@ func (hs *HTTPServer) addHelpLinks(navTree []*dtos.NavLink, c *models.ReqContext return navTree } -func (hs *HTTPServer) buildSavedItemsNavLinks(c *models.ReqContext) ([]*dtos.NavLink, error) { +func (hs *HTTPServer) buildSavedItemsNavLinks(c *models.ReqContext, prefs *pref.Preference) ([]*dtos.NavLink, error) { savedItemsChildNavs := []*dtos.NavLink{} // query preferences table for any saved items - prefsQuery := models.GetPreferencesWithDefaultsQuery{User: c.SignedInUser} - if err := hs.SQLStore.GetPreferencesWithDefaults(c.Req.Context(), &prefsQuery); err != nil { - return nil, err - } - savedItems := prefsQuery.Result.JsonData.Navbar.SavedItems + savedItems := prefs.JSONData.Navbar.SavedItems if len(savedItems) > 0 { for _, savedItem := range savedItems { savedItemsChildNavs = append(savedItemsChildNavs, &dtos.NavLink{ - Id: savedItem.Id, + Id: savedItem.ID, Text: savedItem.Text, Url: savedItem.Url, Target: savedItem.Target, @@ -480,54 +485,70 @@ func (hs *HTTPServer) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b return dashboardChildNavs } -func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext, uaVisibleForOrg bool, hasEditPerm bool) []*dtos.NavLink { +func (hs *HTTPServer) buildLegacyAlertNavLinks() []*dtos.NavLink { + var alertChildNavs []*dtos.NavLink + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Alert rules", Id: "alert-list", Url: hs.Cfg.AppSubURL + "/alerting/list", Icon: "list-ul", + }) + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Notification channels", Id: "channels", Url: hs.Cfg.AppSubURL + "/alerting/notifications", + Icon: "comment-alt-share", + }) + + return []*dtos.NavLink{ + { + Text: "Alerting", + SubTitle: "Alert rules and notifications", + Id: "alerting", + Icon: "bell", + Url: hs.Cfg.AppSubURL + "/alerting/list", + Children: alertChildNavs, + Section: dtos.NavSectionCore, + SortWeight: dtos.WeightAlerting, + }, + } +} + +func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext) []*dtos.NavLink { hasAccess := ac.HasAccess(hs.AccessControl, c) var alertChildNavs []*dtos.NavLink - if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead))) { + if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) { alertChildNavs = append(alertChildNavs, &dtos.NavLink{ Text: "Alert rules", Id: "alert-list", Url: hs.Cfg.AppSubURL + "/alerting/list", Icon: "list-ul", }) } if hasAccess(ac.ReqOrgAdminOrEditor, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsRead), ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead))) { - if uaVisibleForOrg { - alertChildNavs = append(alertChildNavs, &dtos.NavLink{ - Text: "Contact points", Id: "receivers", Url: hs.Cfg.AppSubURL + "/alerting/notifications", - Icon: "comment-alt-share", - }) - alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Notification policies", Id: "am-routes", Url: hs.Cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"}) - } else { - alertChildNavs = append(alertChildNavs, &dtos.NavLink{ - Text: "Notification channels", Id: "channels", Url: hs.Cfg.AppSubURL + "/alerting/notifications", - Icon: "comment-alt-share", - }) - } + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Contact points", Id: "receivers", Url: hs.Cfg.AppSubURL + "/alerting/notifications", + Icon: "comment-alt-share", + }) + alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Notification policies", Id: "am-routes", Url: hs.Cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"}) } - if uaVisibleForOrg && hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) { + if hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) { alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Silences", Id: "silences", Url: hs.Cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"}) alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Alert groups", Id: "groups", Url: hs.Cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"}) } - if c.OrgRole == models.ROLE_ADMIN && uaVisibleForOrg { + if c.OrgRole == models.ROLE_ADMIN { alertChildNavs = append(alertChildNavs, &dtos.NavLink{ Text: "Admin", Id: "alerting-admin", Url: hs.Cfg.AppSubURL + "/alerting/admin", Icon: "cog", }) } - if hs.Features.IsEnabled(featuremgmt.FlagNewNavigation) { - if uaVisibleForOrg && hasEditPerm && hasAccess(ac.ReqSignedIn, ac.EvalPermission(ac.ActionAlertingRuleCreate)) { - alertChildNavs = append(alertChildNavs, &dtos.NavLink{ - Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, - }) + if hs.Features.IsEnabled(featuremgmt.FlagNewNavigation) && + hasAccess(hs.editorInAnyFolder, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) { + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, + }) - alertChildNavs = append(alertChildNavs, &dtos.NavLink{ - Text: "Alert rule", SubTitle: "Create an alert rule", Id: "alert", - Icon: "plus", Url: hs.Cfg.AppSubURL + "/alerting/new", HideFromTabs: true, ShowIconInNavbar: true, - }) - } + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Alert rule", SubTitle: "Create an alert rule", Id: "alert", + Icon: "plus", Url: hs.Cfg.AppSubURL + "/alerting/new", HideFromTabs: true, ShowIconInNavbar: true, + }) } if len(alertChildNavs) > 0 { @@ -572,7 +593,7 @@ func (hs *HTTPServer) buildCreateNavLinks(c *models.ReqContext) []*dtos.NavLink _, uaIsDisabledForOrg := hs.Cfg.UnifiedAlerting.DisabledOrgs[c.OrgId] uaVisibleForOrg := hs.Cfg.UnifiedAlerting.IsEnabled() && !uaIsDisabledForOrg - if uaVisibleForOrg { + if uaVisibleForOrg && hasAccess(ac.ReqSignedIn, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleCreate), ac.EvalPermission(ac.ActionAlertingRuleExternalWrite))) { children = append(children, &dtos.NavLink{ Text: "Alert rule", SubTitle: "Create an alert rule", Id: "alert", Icon: "bell", Url: hs.Cfg.AppSubURL + "/alerting/new", @@ -620,15 +641,17 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink { return adminNavLinks } +func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool { + hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} + if err := hs.SQLStore.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil { + return false + } + return hasEditPermissionInFoldersQuery.Result +} + func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewData, error) { hasAccess := ac.HasAccess(hs.AccessControl, c) - hasEditPerm := hasAccess(func(context *models.ReqContext) bool { - hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} - if err := hs.SQLStore.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil { - return false - } - return hasEditPermissionInFoldersQuery.Result - }, ac.EvalAny(ac.EvalPermission(ac.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionFoldersCreate))) + hasEditPerm := hasAccess(hs.editorInAnyFolder, ac.EvalAny(ac.EvalPermission(ac.ActionDashboardsCreate), ac.EvalPermission(dashboards.ActionFoldersCreate))) settings, err := hs.getFrontendSettingsMap(c) if err != nil { @@ -637,11 +660,11 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat settings["dateFormats"] = hs.Cfg.DateFormats - prefsQuery := models.GetPreferencesWithDefaultsQuery{User: c.SignedInUser} - if err := hs.SQLStore.GetPreferencesWithDefaults(c.Req.Context(), &prefsQuery); err != nil { + prefsQuery := pref.GetPreferenceWithDefaultsQuery{UserID: c.UserId, OrgID: c.OrgId, Teams: c.Teams} + prefs, err := hs.preferenceService.GetWithDefaults(c.Req.Context(), &prefsQuery) + if err != nil { return nil, err } - prefs := prefsQuery.Result // Read locale from accept-language acceptLang := c.Req.Header.Get("Accept-Language") @@ -662,7 +685,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat settings["appSubUrl"] = "" } - navTree, err := hs.getNavTree(c, hasEditPerm) + navTree, err := hs.getNavTree(c, hasEditPerm, prefs) if err != nil { return nil, err } @@ -710,7 +733,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat LoadingLogo: "public/img/grafana_icon.svg", } - if hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !hs.AccessControl.IsDisabled() { userPermissions, err := hs.AccessControl.GetUserPermissions(c.Req.Context(), c.SignedInUser, ac.Options{ReloadCache: false}) if err != nil { return nil, err diff --git a/pkg/api/ldap_debug.go b/pkg/api/ldap_debug.go index 97b1e5142fe..f62daaa0407 100644 --- a/pkg/api/ldap_debug.go +++ b/pkg/api/ldap_debug.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" @@ -240,7 +241,7 @@ func (hs *HTTPServer) GetUserFromLDAP(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err) } - ldap := newLDAP(ldapConfig.Servers) + multiLDAP := newLDAP(ldapConfig.Servers) username := web.Params(c.Req)[":username"] @@ -248,9 +249,8 @@ func (hs *HTTPServer) GetUserFromLDAP(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "Validation error. You must specify an username", nil) } - user, serverConfig, err := ldap.User(username) - - if user == nil { + user, serverConfig, err := multiLDAP.User(username) + if user == nil || err != nil { return response.Error(http.StatusNotFound, "No user was found in the LDAP server(s) with that username", err) } @@ -267,45 +267,32 @@ func (hs *HTTPServer) GetUserFromLDAP(c *models.ReqContext) response.Response { IsDisabled: user.IsDisabled, } - orgRoles := []LDAPRoleDTO{} - - // Need to iterate based on the config groups as only the first match for an org is used - // We are showing all matches as that should help in understanding why one match wins out - // over another. - for _, configGroup := range serverConfig.Groups { - for _, userGroup := range user.Groups { - if configGroup.GroupDN == userGroup { - r := &LDAPRoleDTO{GroupDN: configGroup.GroupDN, OrgId: configGroup.OrgId, OrgRole: configGroup.OrgRole} - orgRoles = append(orgRoles, *r) - break - } - } - //} - } - - // Then, we find what we did not match by inspecting the list of groups returned from - // LDAP against what we have already matched above. + unmappedUserGroups := map[string]struct{}{} for _, userGroup := range user.Groups { - var matched bool + unmappedUserGroups[strings.ToLower(userGroup)] = struct{}{} + } - for _, orgRole := range orgRoles { - if orgRole.GroupDN == userGroup { // we already matched it - matched = true - break - } + orgRolesMap := map[int64]models.RoleType{} + for _, group := range serverConfig.Groups { + // only use the first match for each org + if orgRolesMap[group.OrgId] != "" { + continue } - if !matched { - r := &LDAPRoleDTO{GroupDN: userGroup} - orgRoles = append(orgRoles, *r) + if ldap.IsMemberOf(user.Groups, group.GroupDN) { + orgRolesMap[group.OrgId] = group.OrgRole + u.OrgRoles = append(u.OrgRoles, LDAPRoleDTO{GroupDN: group.GroupDN, + OrgId: group.OrgId, OrgRole: group.OrgRole}) + delete(unmappedUserGroups, strings.ToLower(group.GroupDN)) } } - u.OrgRoles = orgRoles + for userGroup := range unmappedUserGroups { + u.OrgRoles = append(u.OrgRoles, LDAPRoleDTO{GroupDN: userGroup}) + } ldapLogger.Debug("mapping org roles", "orgsRoles", u.OrgRoles) - err = u.FetchOrgs(c.Req.Context(), hs.SQLStore) - if err != nil { + if err := u.FetchOrgs(c.Req.Context(), hs.SQLStore); err != nil { return response.Error(http.StatusBadRequest, "An organization was not found - Please verify your LDAP configuration", err) } diff --git a/pkg/api/org.go b/pkg/api/org.go index d6cd5b3799b..797cf7de76b 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -90,7 +89,7 @@ func (hs *HTTPServer) CreateOrg(c *models.ReqContext) response.Response { if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - acEnabled := hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) + acEnabled := !hs.AccessControl.IsDisabled() if !acEnabled && !(setting.AllowUserOrgCreate || c.IsGrafanaAdmin) { return response.Error(403, "Access denied", nil) } diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go index 3792bee5a90..e95003bdff6 100644 --- a/pkg/api/preferences.go +++ b/pkg/api/preferences.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" + pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/web" ) @@ -18,14 +19,14 @@ const ( // POST /api/preferences/set-home-dash func (hs *HTTPServer) SetHomeDashboard(c *models.ReqContext) response.Response { - cmd := models.SavePreferencesCommand{} + cmd := pref.SavePreferenceCommand{} if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.UserId = c.UserId - cmd.OrgId = c.OrgId + cmd.UserID = c.UserId + cmd.OrgID = c.OrgId - if err := hs.SQLStore.SavePreferences(c.Req.Context(), &cmd); err != nil { + if err := hs.preferenceService.Save(c.Req.Context(), &cmd); err != nil { return response.Error(500, "Failed to set home dashboard", err) } @@ -38,22 +39,23 @@ func (hs *HTTPServer) GetUserPreferences(c *models.ReqContext) response.Response } func (hs *HTTPServer) getPreferencesFor(ctx context.Context, orgID, userID, teamID int64) response.Response { - prefsQuery := models.GetPreferencesQuery{UserId: userID, OrgId: orgID, TeamId: teamID} + prefsQuery := pref.GetPreferenceQuery{UserID: userID, OrgID: orgID, TeamID: teamID} - if err := hs.SQLStore.GetPreferences(ctx, &prefsQuery); err != nil { + preference, err := hs.preferenceService.Get(ctx, &prefsQuery) + if err != nil { return response.Error(500, "Failed to get preferences", err) } dto := dtos.Prefs{ - Theme: prefsQuery.Result.Theme, - HomeDashboardID: prefsQuery.Result.HomeDashboardId, - Timezone: prefsQuery.Result.Timezone, - WeekStart: prefsQuery.Result.WeekStart, + Theme: preference.Theme, + HomeDashboardID: preference.HomeDashboardID, + Timezone: preference.Timezone, + WeekStart: preference.WeekStart, } - if prefsQuery.Result.JsonData != nil { - dto.Navbar = prefsQuery.Result.JsonData.Navbar - dto.QueryHistory = prefsQuery.Result.JsonData.QueryHistory + if preference.JSONData != nil { + dto.Navbar = preference.JSONData.Navbar + dto.QueryHistory = preference.JSONData.QueryHistory } return response.JSON(http.StatusOK, &dto) @@ -72,17 +74,17 @@ func (hs *HTTPServer) updatePreferencesFor(ctx context.Context, orgID, userID, t if dtoCmd.Theme != lightTheme && dtoCmd.Theme != darkTheme && dtoCmd.Theme != defaultTheme { return response.Error(400, "Invalid theme", nil) } - saveCmd := models.SavePreferencesCommand{ - UserId: userID, - OrgId: orgID, - TeamId: teamId, + saveCmd := pref.SavePreferenceCommand{ + UserID: userID, + OrgID: orgID, + TeamID: teamId, Theme: dtoCmd.Theme, Timezone: dtoCmd.Timezone, WeekStart: dtoCmd.WeekStart, - HomeDashboardId: dtoCmd.HomeDashboardID, + HomeDashboardID: dtoCmd.HomeDashboardID, } - if err := hs.SQLStore.SavePreferences(ctx, &saveCmd); err != nil { + if err := hs.preferenceService.Save(ctx, &saveCmd); err != nil { return response.Error(500, "Failed to save preferences", err) } @@ -102,19 +104,19 @@ func (hs *HTTPServer) patchPreferencesFor(ctx context.Context, orgID, userID, te if dtoCmd.Theme != nil && *dtoCmd.Theme != lightTheme && *dtoCmd.Theme != darkTheme && *dtoCmd.Theme != defaultTheme { return response.Error(400, "Invalid theme", nil) } - patchCmd := models.PatchPreferencesCommand{ - UserId: userID, - OrgId: orgID, - TeamId: teamId, + patchCmd := pref.PatchPreferenceCommand{ + UserID: userID, + OrgID: orgID, + TeamID: teamId, Theme: dtoCmd.Theme, Timezone: dtoCmd.Timezone, WeekStart: dtoCmd.WeekStart, - HomeDashboardId: dtoCmd.HomeDashboardID, + HomeDashboardID: dtoCmd.HomeDashboardID, Navbar: dtoCmd.Navbar, QueryHistory: dtoCmd.QueryHistory, } - if err := hs.SQLStore.PatchPreferences(ctx, &patchCmd); err != nil { + if err := hs.preferenceService.Patch(ctx, &patchCmd); err != nil { return response.Error(500, "Failed to save preferences", err) } diff --git a/pkg/api/preferences_test.go b/pkg/api/preferences_test.go index 98cb46f14dd..4a52fb0fd97 100644 --- a/pkg/api/preferences_test.go +++ b/pkg/api/preferences_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/grafana/grafana/pkg/services/accesscontrol" + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/services/preference/preftest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,6 +27,9 @@ var ( func TestAPIEndpoint_GetCurrentOrgPreferences_LegacyAccessControl(t *testing.T) { sc := setupHTTPServer(t, true, false) + prefService := preftest.NewPreferenceServiceFake() + prefService.ExpectedPreference = &pref.Preference{HomeDashboardID: 1, Theme: "dark"} + sc.hs.preferenceService = prefService _, err := sc.db.CreateOrgWithMember("TestOrg", testUserID) require.NoError(t, err) @@ -46,6 +51,10 @@ func TestAPIEndpoint_GetCurrentOrgPreferences_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true, true) setInitCtxSignedInViewer(sc.initCtx) + prefService := preftest.NewPreferenceServiceFake() + prefService.ExpectedPreference = &pref.Preference{HomeDashboardID: 1, Theme: "dark"} + sc.hs.preferenceService = prefService + _, err := sc.db.CreateOrgWithMember("TestOrg", testUserID) require.NoError(t, err) diff --git a/pkg/api/team.go b/pkg/api/team.go index 79c0eac9982..cfd44400e06 100644 --- a/pkg/api/team.go +++ b/pkg/api/team.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -19,7 +18,7 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response { if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - accessControlEnabled := hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) + accessControlEnabled := !hs.AccessControl.IsDisabled() if !accessControlEnabled && c.OrgRole == models.ROLE_VIEWER { return response.Error(403, "Not allowed to create team.", nil) } @@ -63,7 +62,7 @@ func (hs *HTTPServer) UpdateTeam(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "teamId is invalid", err) } - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), cmd.OrgId, cmd.Id, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to update team", err) } @@ -88,7 +87,7 @@ func (hs *HTTPServer) DeleteTeamByID(c *models.ReqContext) response.Response { } user := c.SignedInUser - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), orgId, teamId, user); err != nil { return response.Error(403, "Not allowed to delete team", err) } @@ -116,7 +115,7 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) response.Response { // Using accesscontrol the filtering is done based on user permissions userIdFilter := models.FilterIgnoreUser - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { userIdFilter = userFilter(c) } @@ -174,7 +173,7 @@ func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) response.Response { // Using accesscontrol the filtering has already been performed at middleware layer userIdFilter := models.FilterIgnoreUser - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { userIdFilter = userFilter(c) } @@ -210,7 +209,7 @@ func (hs *HTTPServer) GetTeamPreferences(c *models.ReqContext) response.Response orgId := c.OrgId - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), orgId, teamId, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to view team preferences.", err) } @@ -233,7 +232,7 @@ func (hs *HTTPServer) UpdateTeamPreferences(c *models.ReqContext) response.Respo orgId := c.OrgId - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), orgId, teamId, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to update team preferences.", err) } diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index a8c44cde66a..f0a7576da64 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -27,7 +26,7 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) response.Response { // With accesscontrol the permission check has been done at middleware layer // and the membership filtering will be done at DB layer based on user permissions - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), query.OrgId, query.TeamId, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to list team members", err) } @@ -70,7 +69,7 @@ func (hs *HTTPServer) AddTeamMember(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "teamId is invalid", err) } - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), cmd.OrgId, cmd.TeamId, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to add team member", err) } @@ -110,7 +109,7 @@ func (hs *HTTPServer) UpdateTeamMember(c *models.ReqContext) response.Response { } orgId := c.OrgId - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), orgId, teamId, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to update team member", err) } @@ -153,7 +152,7 @@ func (hs *HTTPServer) RemoveTeamMember(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "userId is invalid", err) } - if !hs.Features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if hs.AccessControl.IsDisabled() { if err := hs.teamGuardian.CanAdmin(c.Req.Context(), orgId, teamId, c.SignedInUser); err != nil { return response.Error(403, "Not allowed to remove team member", err) } diff --git a/pkg/api/team_members_test.go b/pkg/api/team_members_test.go index a5089aa71fc..b64f7a25a34 100644 --- a/pkg/api/team_members_test.go +++ b/pkg/api/team_members_test.go @@ -191,7 +191,7 @@ func TestAddTeamMembersAPIEndpoint_LegacyAccessControl(t *testing.T) { }) } -func TestGetTeamMembersAPIEndpoint_FGAC(t *testing.T) { +func TestGetTeamMembersAPIEndpoint_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.hs.License = &licensing.OSSLicensingService{} @@ -245,7 +245,7 @@ func TestGetTeamMembersAPIEndpoint_FGAC(t *testing.T) { }) } -func TestAddTeamMembersAPIEndpoint_FGAC(t *testing.T) { +func TestAddTeamMembersAPIEndpoint_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.hs.License = &licensing.OSSLicensingService{} @@ -325,7 +325,7 @@ func TestUpdateTeamMembersAPIEndpoint_LegacyAccessControl(t *testing.T) { }) } -func TestUpdateTeamMembersAPIEndpoint_FGAC(t *testing.T) { +func TestUpdateTeamMembersAPIEndpoint_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.hs.License = &licensing.OSSLicensingService{} @@ -399,7 +399,7 @@ func TestDeleteTeamMembersAPIEndpoint_LegacyAccessControl(t *testing.T) { }) } -func TestDeleteTeamMembersAPIEndpoint_FGAC(t *testing.T) { +func TestDeleteTeamMembersAPIEndpoint_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.hs.License = &licensing.OSSLicensingService{} diff --git a/pkg/api/team_test.go b/pkg/api/team_test.go index bf2c5ae631f..90fc4a24e91 100644 --- a/pkg/api/team_test.go +++ b/pkg/api/team_test.go @@ -11,6 +11,8 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/services/preference/preftest" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" @@ -196,7 +198,7 @@ func TestTeamAPIEndpoint_CreateTeam_LegacyAccessControl_EditorsCanAdmin(t *testi }) } -func TestTeamAPIEndpoint_CreateTeam_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_CreateTeam_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) setInitCtxSignedInViewer(sc.initCtx) @@ -215,7 +217,7 @@ func TestTeamAPIEndpoint_CreateTeam_FGAC(t *testing.T) { }) } -func TestTeamAPIEndpoint_SearchTeams_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_SearchTeams_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) // Seed three teams for i := 1; i <= 3; i++ { @@ -259,7 +261,7 @@ func TestTeamAPIEndpoint_SearchTeams_FGAC(t *testing.T) { }) } -func TestTeamAPIEndpoint_GetTeamByID_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_GetTeamByID_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.db = sqlstore.InitTestDB(t) @@ -289,7 +291,7 @@ func TestTeamAPIEndpoint_GetTeamByID_FGAC(t *testing.T) { // Given a team with a user, when the user is granted X permission, // Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsWrite with teams:id:1 scope // else return 403 -func TestTeamAPIEndpoint_UpdateTeam_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_UpdateTeam_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.db = sqlstore.InitTestDB(t) _, err := sc.db.CreateTeam("team1", "", 1) @@ -338,7 +340,7 @@ func TestTeamAPIEndpoint_UpdateTeam_FGAC(t *testing.T) { // Given a team with a user, when the user is granted X permission, // Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsDelete with teams:id:1 scope // else return 403 -func TestTeamAPIEndpoint_DeleteTeam_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_DeleteTeam_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.db = sqlstore.InitTestDB(t) _, err := sc.db.CreateTeam("team1", "", 1) @@ -370,11 +372,15 @@ func TestTeamAPIEndpoint_DeleteTeam_FGAC(t *testing.T) { // Given a team with a user, when the user is granted X permission, // Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsRead with teams:id:1 scope // else return 403 -func TestTeamAPIEndpoint_GetTeamPreferences_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_GetTeamPreferences_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) sc.db = sqlstore.InitTestDB(t) _, err := sc.db.CreateTeam("team1", "", 1) + prefService := preftest.NewPreferenceServiceFake() + prefService.ExpectedPreference = &pref.Preference{} + sc.hs.preferenceService = prefService + require.NoError(t, err) setInitCtxSignedInViewer(sc.initCtx) @@ -396,9 +402,15 @@ func TestTeamAPIEndpoint_GetTeamPreferences_FGAC(t *testing.T) { // Given a team with a user, when the user is granted X permission, // Then the endpoint should return 200 if the user has accesscontrol.ActionTeamsWrite with teams:id:1 scope // else return 403 -func TestTeamAPIEndpoint_UpdateTeamPreferences_FGAC(t *testing.T) { +func TestTeamAPIEndpoint_UpdateTeamPreferences_RBAC(t *testing.T) { sc := setupHTTPServer(t, true, true) - sc.db = sqlstore.InitTestDB(t) + sqlStore := sqlstore.InitTestDB(t) + sc.db = sqlStore + + prefService := preftest.NewPreferenceServiceFake() + prefService.ExpectedPreference = &pref.Preference{Theme: "dark"} + sc.hs.preferenceService = prefService + _, err := sc.db.CreateTeam("team1", "", 1) require.NoError(t, err) @@ -411,10 +423,10 @@ func TestTeamAPIEndpoint_UpdateTeamPreferences_FGAC(t *testing.T) { response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(detailTeamPreferenceURL, 1), input, t) assert.Equal(t, http.StatusOK, response.Code) - prefQuery := &models.GetPreferencesQuery{OrgId: 1, TeamId: 1, Result: &models.Preferences{}} - err := sc.db.GetPreferences(context.Background(), prefQuery) + prefQuery := &pref.GetPreferenceQuery{OrgID: 1, TeamID: 1} + preference, err := prefService.Get(context.Background(), prefQuery) require.NoError(t, err) - assert.Equal(t, "dark", prefQuery.Result.Theme) + assert.Equal(t, "dark", preference.Theme) }) input = strings.NewReader(teamPreferenceCmdLight) @@ -423,9 +435,9 @@ func TestTeamAPIEndpoint_UpdateTeamPreferences_FGAC(t *testing.T) { response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(detailTeamPreferenceURL, 1), input, t) assert.Equal(t, http.StatusForbidden, response.Code) - prefQuery := &models.GetPreferencesQuery{OrgId: 1, TeamId: 1, Result: &models.Preferences{}} - err := sc.db.GetPreferences(context.Background(), prefQuery) + prefQuery := &pref.GetPreferenceQuery{OrgID: 1, TeamID: 1} + preference, err := prefService.Get(context.Background(), prefQuery) assert.NoError(t, err) - assert.Equal(t, "dark", prefQuery.Result.Theme) + assert.Equal(t, "dark", preference.Theme) }) } diff --git a/pkg/infra/filestorage/api.go b/pkg/infra/filestorage/api.go index 93515f2c30e..0a1712e8657 100644 --- a/pkg/infra/filestorage/api.go +++ b/pkg/infra/filestorage/api.go @@ -60,13 +60,6 @@ type UpsertFileCommand struct { Properties map[string]string } -type PathFilters struct { - allowedPrefixes []string - disallowedPrefixes []string - allowedPaths []string - disallowedPaths []string -} - func toLower(list []string) []string { if list == nil { return nil @@ -78,63 +71,6 @@ func toLower(list []string) []string { return lower } -func allowAllPathFilters() *PathFilters { - return NewPathFilters(nil, nil, nil, nil) -} - -//nolint:deadcode,unused -func denyAllPathFilters() *PathFilters { - return NewPathFilters([]string{}, []string{}, nil, nil) -} - -func NewPathFilters(allowedPrefixes []string, allowedPaths []string, disallowedPrefixes []string, disallowedPaths []string) *PathFilters { - return &PathFilters{ - allowedPrefixes: toLower(allowedPrefixes), - allowedPaths: toLower(allowedPaths), - disallowedPaths: toLower(disallowedPaths), - disallowedPrefixes: toLower(disallowedPrefixes), - } -} - -func (f *PathFilters) isDenyAll() bool { - return f.allowedPaths != nil && f.allowedPrefixes != nil && (len(f.allowedPaths)+len(f.allowedPrefixes) == 0) -} - -func (f *PathFilters) IsAllowed(path string) bool { - if f == nil { - return true - } - - path = strings.ToLower(path) - for i := range f.disallowedPaths { - if f.disallowedPaths[i] == path { - return false - } - } - - for i := range f.disallowedPrefixes { - if strings.HasPrefix(path, f.disallowedPrefixes[i]) { - return false - } - } - - if f.allowedPrefixes == nil && f.allowedPaths == nil { - return true - } - - for i := range f.allowedPaths { - if f.allowedPaths[i] == path { - return true - } - } - for i := range f.allowedPrefixes { - if strings.HasPrefix(path, f.allowedPrefixes[i]) { - return true - } - } - return false -} - type ListResponse struct { Files []*File HasMore bool @@ -146,7 +82,7 @@ type ListOptions struct { WithFiles bool WithFolders bool WithContents bool - *PathFilters + Filter PathFilter } type FileStorage interface { diff --git a/pkg/infra/filestorage/cdk_blob_filestorage.go b/pkg/infra/filestorage/cdk_blob_filestorage.go index 53f165fac9a..988b8749497 100644 --- a/pkg/infra/filestorage/cdk_blob_filestorage.go +++ b/pkg/infra/filestorage/cdk_blob_filestorage.go @@ -24,11 +24,11 @@ type cdkBlobStorage struct { bucket *blob.Bucket } -func NewCdkBlobStorage(log log.Logger, bucket *blob.Bucket, rootFolder string, pathFilters *PathFilters) FileStorage { +func NewCdkBlobStorage(log log.Logger, bucket *blob.Bucket, rootFolder string, filter PathFilter) FileStorage { return newWrapper(log, &cdkBlobStorage{ log: log, bucket: bucket, - }, pathFilters, rootFolder) + }, filter, rootFolder) } func (c cdkBlobStorage) Get(ctx context.Context, filePath string) (*File, error) { @@ -266,7 +266,7 @@ func (c cdkBlobStorage) list(ctx context.Context, folderPath string, paging *Pag path := obj.Key lowerPath := strings.ToLower(path) - allowed := options.IsAllowed(lowerPath) + allowed := options.Filter.IsAllowed(lowerPath) if obj.IsDir && recursive && !visitedFolders[lowerPath] { iterators = append([]*blob.ListIterator{c.bucket.List(&blob.ListOptions{ diff --git a/pkg/infra/filestorage/db_filestorage.go b/pkg/infra/filestorage/db_filestorage.go index ae4db90b713..3d826af9095 100644 --- a/pkg/infra/filestorage/db_filestorage.go +++ b/pkg/infra/filestorage/db_filestorage.go @@ -31,11 +31,11 @@ type dbFileStorage struct { log log.Logger } -func NewDbStorage(log log.Logger, db *sqlstore.SQLStore, pathFilters *PathFilters, rootFolder string) FileStorage { +func NewDbStorage(log log.Logger, db *sqlstore.SQLStore, filter PathFilter, rootFolder string) FileStorage { return newWrapper(log, &dbFileStorage{ log: log, db: db, - }, pathFilters, rootFolder) + }, filter, rootFolder) } func (s dbFileStorage) getProperties(sess *sqlstore.DBSession, lowerCasePaths []string) (map[string]map[string]string, error) { @@ -268,40 +268,8 @@ func (s dbFileStorage) List(ctx context.Context, folderPath string, paging *Pagi sess.Where("path LIKE ?", "%/") } - if len(options.PathFilters.allowedPrefixes)+len(options.PathFilters.allowedPaths) > 0 { - queries := make([]string, 0) - args := make([]interface{}, 0) - for _, prefix := range options.PathFilters.allowedPrefixes { - queries = append(queries, "LOWER(path) LIKE ?") - args = append(args, prefix+"%") - } - - for _, path := range options.PathFilters.allowedPaths { - queries = append(queries, "LOWER(path) = ?") - args = append(args, path) - } - sess.Where(strings.Join(queries, " OR "), args...) - } - - if options.PathFilters.disallowedPrefixes != nil && len(options.PathFilters.disallowedPrefixes) > 0 { - queries := make([]string, 0) - args := make([]interface{}, 0) - for _, prefix := range options.PathFilters.disallowedPrefixes { - queries = append(queries, "LOWER(path) NOT LIKE ?") - args = append(args, prefix+"%") - } - sess.Where(strings.Join(queries, " AND "), args...) - } - - if options.PathFilters.disallowedPaths != nil && len(options.PathFilters.disallowedPaths) > 0 { - queries := make([]string, 0) - args := make([]interface{}, 0) - for _, path := range options.PathFilters.disallowedPaths { - queries = append(queries, "LOWER(path) != ?") - args = append(args, path) - } - sess.Where(strings.Join(queries, " AND "), args...) - } + sqlFilter := options.Filter.asSQLFilter() + sess.Where(sqlFilter.Where, sqlFilter.Args...) sess.OrderBy("path") diff --git a/pkg/infra/filestorage/filter.go b/pkg/infra/filestorage/filter.go new file mode 100644 index 00000000000..ce6ec130ad2 --- /dev/null +++ b/pkg/infra/filestorage/filter.go @@ -0,0 +1,249 @@ +package filestorage + +import ( + "strings" + + "github.com/armon/go-radix" + "github.com/grafana/grafana/pkg/services/accesscontrol" +) + +type treeValue string + +const ( + treeValueDisallowedPath treeValue = "!" + treeValueDisallowedPrefix treeValue = "!*" + treeValueAllowedPath treeValue = "" + treeValueAllowedPrefix treeValue = "*" +) + +type PathFilter interface { + IsAllowed(path string) bool + ToString() string + + asSQLFilter() accesscontrol.SQLFilter +} + +// NewPathFilter factory function for a tree-based PathFilter +// `nil` and empty arrays are treated in a different way. The PathFilter will not perform checks associated with a `nil` array, examples: +// - `allowedPrefixes` & `allowedPaths` are nil -> all paths are allowed (unless included in `disallowedX` arrays) +// - `allowedPrefixes` & `allowedPaths` are both empty -> no paths are allowed, regardless of what is inside `disallowedX` arrays +// - `allowedPrefixes` is nil, `allowedPaths` is not nil. -> only paths specified in `allowedPaths` are allowed (unless included in `disallowedX` arrays) +func NewPathFilter(allowedPrefixes []string, allowedPaths []string, disallowedPrefixes []string, disallowedPaths []string) PathFilter { + if allowedPrefixes != nil && allowedPaths != nil && (len(allowedPrefixes)+len(allowedPaths) == 0) { + return NewDenyAllPathFilter() + } + + if allowedPaths == nil && allowedPrefixes == nil && (len(disallowedPaths)+len(disallowedPrefixes) == 0) { + return NewAllowAllPathFilter() + } + + return &radixTreePathFilter{ + tree: createRadixTree(toLower(allowedPrefixes), toLower(allowedPaths), toLower(disallowedPrefixes), toLower(disallowedPaths)), + } +} + +func NewAllowAllPathFilter() PathFilter { + return &radixTreePathFilter{ + tree: createRadixTree([]string{Delimiter}, nil, nil, nil), + } +} + +func NewDenyAllPathFilter() PathFilter { + return &radixTreePathFilter{ + tree: createRadixTree(nil, nil, []string{Delimiter}, nil), + } +} + +type radixTreePathFilter struct { + tree *radix.Tree +} + +func (r *radixTreePathFilter) asSQLFilter() accesscontrol.SQLFilter { + denyConditions := make([]string, 0) + denyArgs := make([]interface{}, 0) + + allowConditions := make([]string, 0) + allowArgs := make([]interface{}, 0) + + r.tree.Walk(func(path string, v interface{}) bool { + switch v.(treeValue) { + case treeValueAllowedPrefix: + allowConditions = append(allowConditions, "LOWER(PATH) LIKE ?") + allowArgs = append(allowArgs, path+"%") + case treeValueAllowedPath: + allowConditions = append(allowConditions, "LOWER(PATH) = ?") + allowArgs = append(allowArgs, path) + case treeValueDisallowedPrefix: + denyConditions = append(denyConditions, "LOWER(PATH) NOT LIKE ? ") + denyArgs = append(denyArgs, path+"%") + case treeValueDisallowedPath: + denyConditions = append(denyConditions, "LOWER(PATH) != ?") + denyArgs = append(denyArgs, path) + } + return false + }) + + if len(denyConditions)+len(allowConditions) == 0 { + return accesscontrol.SQLFilter{Where: "1 = 1", Args: nil} + } + + allowQuery := strings.Join(allowConditions, " OR ") + denyQuery := strings.Join(denyConditions, " AND ") + + if len(allowConditions) == 0 { + return accesscontrol.SQLFilter{ + Where: denyQuery, + Args: denyArgs, + } + } + + if len(denyConditions) == 0 { + return accesscontrol.SQLFilter{ + Where: allowQuery, + Args: allowArgs, + } + } + + return accesscontrol.SQLFilter{ + Where: "(" + allowQuery + ") AND ( " + denyQuery + " )", + Args: append(allowArgs, denyArgs...), + } +} + +func (r *radixTreePathFilter) ToString() string { + builder := strings.Builder{} + r.tree.Walk(func(s string, v interface{}) bool { + if builder.Len() != 0 { + builder.WriteString("\n") + } + switch v.(treeValue) { + case treeValueAllowedPrefix: + builder.WriteString(s) + builder.WriteString("*") + case treeValueAllowedPath: + builder.WriteString(s) + case treeValueDisallowedPrefix: + builder.WriteString("!") + builder.WriteString(s) + builder.WriteString("*") + case treeValueDisallowedPath: + builder.WriteString("!") + builder.WriteString(s) + } + return false + }) + return builder.String() +} + +func (r *radixTreePathFilter) IsAllowed(path string) bool { + path = strings.ToLower(path) + + allowed := false + denied := false + r.tree.WalkPath(path, func(s string, v interface{}) bool { + if v == treeValueDisallowedPrefix || (s == path && v == treeValueDisallowedPath) { + denied = true + return true + } + + if v == treeValueAllowedPrefix || (s == path && v == treeValueAllowedPath) { + allowed = true + // have to keep traversing to look for explicit denies + } + return false + }) + + if denied { + return false + } + + return allowed +} + +func createRadixTree(allowedPrefixes []string, allowedPaths []string, disallowedPrefixes []string, disallowedPaths []string) *radix.Tree { + tree := radix.New() + for _, disallowedPrefix := range disallowedPrefixes { + tree.Insert(disallowedPrefix, treeValueDisallowedPrefix) + } + + for _, disallowedPath := range disallowedPaths { + tree.Insert(disallowedPath, treeValueDisallowedPath) + } + + for _, allowedPath := range allowedPaths { + isDenied := false + tree.WalkPath(allowedPath, func(s string, v interface{}) bool { + if v == treeValueDisallowedPrefix || s == allowedPath { + isDenied = true + return true + } + return false + }) + + if !isDenied { + tree.Insert(allowedPath, treeValueAllowedPath) + } + } + + for _, allowedPrefix := range allowedPrefixes { + isDenied := false + tree.WalkPath(allowedPrefix, func(s string, v interface{}) bool { + if v == treeValueDisallowedPrefix { + isDenied = true + return true + } + return false + }) + + if !isDenied { + tree.Insert(allowedPrefix, treeValueAllowedPrefix) + } + } + + return tree +} + +type allOfPathFilter struct { + filters []PathFilter +} + +func (m allOfPathFilter) IsAllowed(path string) bool { + for _, filter := range m.filters { + isAllowed := filter.IsAllowed(path) + if !isAllowed { + return false + } + } + return true +} + +func (m allOfPathFilter) ToString() string { + s := strings.Builder{} + for _, filter := range m.filters { + if s.Len() != 0 { + s.WriteString("\n\nAND\n") + } + s.WriteString(filter.ToString()) + } + return s.String() +} + +func (m allOfPathFilter) asSQLFilter() accesscontrol.SQLFilter { + queries := make([]string, 0) + + args := make([]interface{}, 0) + for _, filter := range m.filters { + sqlFilter := filter.asSQLFilter() + queries = append(queries, "("+sqlFilter.Where+")") + args = append(args, sqlFilter.Args...) + } + + return accesscontrol.SQLFilter{ + Where: "(" + strings.Join(queries, " AND ") + ")", + Args: args, + } +} + +func newAndPathFilter(filters ...PathFilter) PathFilter { + return &allOfPathFilter{filters: filters} +} diff --git a/pkg/infra/filestorage/filter_test.go b/pkg/infra/filestorage/filter_test.go new file mode 100644 index 00000000000..20f69c48870 --- /dev/null +++ b/pkg/infra/filestorage/filter_test.go @@ -0,0 +1,135 @@ +package filestorage + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAuth(t *testing.T) { + var tests = []struct { + name string + filter PathFilter + expectedDeny []string + expectedAllow []string + }{ + { + name: "can not access anything without an allow rule (deny by default)", + filter: NewPathFilter([]string{"/b"}, nil, nil, nil), + expectedDeny: []string{ + "/a/b.jpg", + "/a/", + "/c", + "/", + }, + expectedAllow: []string{ + "/b/", + "/b/a.jpg", + }, + }, + { + name: "can access any path with / prefix and no denies", + filter: NewPathFilter([]string{"/"}, nil, nil, nil), + expectedAllow: []string{ + "/a/b/c/d/e.jpg", + }, + }, + { + name: "can not access paths which are explicitly denied", + filter: NewPathFilter([]string{"/"}, nil, []string{"/x/"}, []string{"/a/b/c/d/e.jpg"}), + expectedDeny: []string{ + "/a/b/c/d/e.jpg", + "/x/", + "/x/a.jpg", + "/x/a/b.jpg", + }, + expectedAllow: []string{ + "/a/b/c/d/x.jpg", + "/a/b/c/d/", + }, + }, + { + name: "can not access paths with denied prefixes - parent folder", + filter: NewPathFilter([]string{"/"}, nil, []string{"/a/b/c/"}, []string{"/a/b/c/d/e.jpg"}), + expectedDeny: []string{ + "/a/b/c/d/e.jpg", + }, + expectedAllow: []string{ + "/a/b/x/d/e.jpg", + }, + }, + { + name: "can not access paths with denied prefixes - root folder", + filter: NewPathFilter([]string{"/"}, nil, []string{"/"}, nil), + expectedDeny: []string{ + "/a/b/c/d/e.jpg", + "/", + "/a.jpg", + }, + }, + { + name: "can not access paths with denied prefixes - same folder", + filter: NewPathFilter([]string{"/"}, nil, []string{"/a/b/c/d/e"}, nil), + expectedDeny: []string{ + "/a/b/c/d/e.jpg", + }, + }, + { + name: "can not access paths with denied prefixes - parent folder with a dot", + filter: NewPathFilter([]string{"/"}, nil, []string{"/a/b/c/d."}, nil), + expectedDeny: []string{ + "/a/b/c/d.e/f.jpg", + }, + expectedAllow: []string{ + "/a/b/c/e.jpg", + }, + }, + { + name: "can not access paths with denied prefixes even if path is explicitly allowed - deny takes priority", + filter: NewPathFilter([]string{"/"}, []string{"/a/b/c/d/f.jpg"}, []string{"/a/"}, nil), + expectedDeny: []string{ + "/a/b/c/d/f.jpg", + }, + }, + { + name: "multiple rules", + filter: NewPathFilter( + []string{"/gitB/", "/s3/folder/", "/gitC/"}, + []string{"/gitA/dashboard2.json"}, + []string{"/s3/folder/nested/"}, + []string{"/gitC/nestedC/"}, + ), + expectedAllow: []string{ + "/gitA/dashboard2.json", + "/gitB/", + "/gitB/nested/", + "/gitB/nested/dashboard.json", + "/gitB/nested2/dashboard2.json", + "/gitC/", + "/gitC/nestedC/dashboardC.json", + "/s3/folder/", + "/s3/folder/file.jpg", + "/s3/folder/nested2/file.jpg", + }, + expectedDeny: []string{ + "/gitA/dashboard.json", // not explicitly allowed + "/s3/folder/nested/dashboard.json", // denied with '/s3/folder/nested/' prefix + "/s3/nestedC/", // not explicitly allowed + "/s3/anyFile.jpg", // not explicitly allowed + "/s3/", // not explicitly allowed + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, expectedAllow := range tt.expectedAllow { + require.Truef(t, tt.filter.IsAllowed(expectedAllow), "expected access to %s", expectedAllow) + } + + for _, expectedDeny := range tt.expectedDeny { + require.Falsef(t, tt.filter.IsAllowed(expectedDeny), "expected no access to %s", expectedDeny) + } + }) + } +} diff --git a/pkg/infra/filestorage/fs_integration_test.go b/pkg/infra/filestorage/fs_integration_test.go index 1a756545a2c..db93761e5d8 100644 --- a/pkg/infra/filestorage/fs_integration_test.go +++ b/pkg/infra/filestorage/fs_integration_test.go @@ -95,7 +95,6 @@ func runTests(createCases func() []fsTestCase, t *testing.T) { setupLocalFsNestedPath := func() { commonSetup() tmpDir, err := ioutil.TempDir("", "") - tempDir = tmpDir if err != nil { t.Fatal(err) } @@ -318,6 +317,13 @@ func TestFsStorage(t *testing.T) { Properties: map[string]string{"prop1": "val1"}, }, }, + cmdUpsert{ + cmd: UpsertFileCommand{ + Path: "/dashboards/dashboards/file-inner.jpg", + Contents: emptyContents, + Properties: map[string]string{"prop1": "val1"}, + }, + }, queryListFiles{ input: queryListFilesInput{path: "/folder1/file-inner.jp", options: &ListOptions{Recursive: true}}, list: checks(listSize(0), listHasMore(false), listLastPath("")), @@ -355,6 +361,13 @@ func TestFsStorage(t *testing.T) { checks(fPath("/folder1/file-inner.jpg"), fName("file-inner.jpg"), fProperties(map[string]string{"prop1": "val1"})), }, }, + queryListFiles{ + input: queryListFilesInput{path: "/dashboards/dashboards/file-inner.jpg", options: &ListOptions{Recursive: true, WithFiles: true}}, + list: checks(listSize(1), listHasMore(false), listLastPath("/dashboards/dashboards/file-inner.jpg")), + files: [][]interface{}{ + checks(fPath("/dashboards/dashboards/file-inner.jpg"), fName("file-inner.jpg")), + }, + }, queryListFiles{ input: queryListFilesInput{path: "/folder1/file-inner.jpg", options: &ListOptions{Recursive: true, WithFolders: true, WithFiles: true}}, list: checks(listSize(1), listHasMore(false), listLastPath("/folder1/file-inner.jpg")), @@ -380,23 +393,23 @@ func TestFsStorage(t *testing.T) { }, }, queryListFiles{ - input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, PathFilters: &PathFilters{allowedPrefixes: []string{"/folder2"}}}}, + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, Filter: NewPathFilter([]string{"/folder2"}, nil, nil, nil)}}, list: checks(listSize(0), listHasMore(false), listLastPath("")), }, queryListFiles{ - input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true, PathFilters: &PathFilters{allowedPrefixes: []string{"/folder2"}}}}, + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true, Filter: NewPathFilter([]string{"/folder2"}, nil, nil, nil)}}, list: checks(listSize(0), listHasMore(false), listLastPath("")), files: [][]interface{}{}, }, queryListFiles{ - input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, PathFilters: &PathFilters{allowedPrefixes: []string{"/folder1/folder"}}}}, + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, Filter: NewPathFilter([]string{"/folder1/folder"}, nil, nil, nil)}}, list: checks(listSize(1), listHasMore(false)), files: [][]interface{}{ checks(fPath("/folder1/folder2/file.jpg")), }, }, queryListFiles{ - input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true, PathFilters: &PathFilters{allowedPrefixes: []string{"/folder1/folder"}}}}, + input: queryListFilesInput{path: "/folder1", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true, Filter: NewPathFilter([]string{"/folder1/folder"}, nil, nil, nil)}}, list: checks(listSize(2), listHasMore(false)), files: [][]interface{}{ checks(fPath("/folder1/folder2"), fMimeType("directory")), @@ -1151,7 +1164,7 @@ func TestFsStorage(t *testing.T) { } createPathFiltersCases := func() []fsTestCase { - pathFilters := NewPathFilters( + pathFilters := NewPathFilter( []string{"/gitB/", "/s3/folder/", "/gitC/"}, []string{"/gitA/dashboard2.json"}, []string{"/s3/folder/nested/"}, @@ -1205,22 +1218,22 @@ func TestFsStorage(t *testing.T) { }, queryListFiles{ input: queryListFilesInput{path: "/", options: &ListOptions{ - Recursive: true, - PathFilters: allowAllPathFilters(), + Recursive: true, + Filter: NewAllowAllPathFilter(), }}, list: checks(listSize(7)), }, queryListFiles{ input: queryListFilesInput{path: "/", options: &ListOptions{ - Recursive: true, - PathFilters: denyAllPathFilters(), + Recursive: true, + Filter: NewDenyAllPathFilter(), }}, list: checks(listSize(0)), }, queryListFiles{ input: queryListFilesInput{path: "/", options: &ListOptions{ - Recursive: true, - PathFilters: pathFilters, + Recursive: true, + Filter: pathFilters, }}, list: checks(listSize(5), listHasMore(false), listLastPath("/s3/folder/dashboard.json")), files: [][]interface{}{ @@ -1236,7 +1249,7 @@ func TestFsStorage(t *testing.T) { queryListFiles{ input: queryListFilesInput{path: "/", options: &ListOptions{ Recursive: true, - PathFilters: pathFilters, + Filter: pathFilters, WithFiles: true, WithFolders: true, }}, @@ -1260,8 +1273,8 @@ func TestFsStorage(t *testing.T) { }, queryListFolders{ input: queryListFoldersInput{path: "/", options: &ListOptions{ - Recursive: true, - PathFilters: pathFilters, + Recursive: true, + Filter: pathFilters, }}, checks: [][]interface{}{ // /gitA is missing due to the lack of explicit allow @@ -1277,8 +1290,8 @@ func TestFsStorage(t *testing.T) { }, queryListFiles{ input: queryListFilesInput{path: "/gitA", options: &ListOptions{ - Recursive: false, - PathFilters: pathFilters, + Recursive: false, + Filter: pathFilters, }}, list: checks(listSize(1), listHasMore(false), listLastPath("/gitA/dashboard2.json")), files: [][]interface{}{ @@ -1287,23 +1300,23 @@ func TestFsStorage(t *testing.T) { }, queryListFolders{ input: queryListFoldersInput{path: "/gitA", options: &ListOptions{ - Recursive: false, - PathFilters: pathFilters, + Recursive: false, + Filter: pathFilters, }}, checks: [][]interface{}{}, }, queryListFiles{ input: queryListFilesInput{path: "/gitC", options: &ListOptions{ - Recursive: false, - PathFilters: pathFilters, + Recursive: false, + Filter: pathFilters, }}, list: checks(listSize(0), listHasMore(false), listLastPath("")), files: [][]interface{}{}, }, queryListFiles{ input: queryListFilesInput{path: "/gitC/nestedC", options: &ListOptions{ - Recursive: false, - PathFilters: pathFilters, + Recursive: false, + Filter: pathFilters, }}, list: checks(listSize(1), listHasMore(false), listLastPath("/gitC/nestedC/dashboardC.json")), files: [][]interface{}{ @@ -1312,8 +1325,8 @@ func TestFsStorage(t *testing.T) { }, queryListFolders{ input: queryListFoldersInput{path: "/gitC", options: &ListOptions{ - Recursive: false, - PathFilters: pathFilters, + Recursive: false, + Filter: pathFilters, }}, checks: [][]interface{}{}, }, diff --git a/pkg/infra/filestorage/test_utils.go b/pkg/infra/filestorage/test_utils.go index 695a774a68e..a8e52ad02ec 100644 --- a/pkg/infra/filestorage/test_utils.go +++ b/pkg/infra/filestorage/test_utils.go @@ -313,7 +313,7 @@ func handleQuery(t *testing.T, ctx context.Context, query interface{}, queryName WithFiles: false, WithFolders: true, WithContents: false, - PathFilters: nil, + Filter: nil, } } else { opts.WithFolders = true diff --git a/pkg/infra/filestorage/wrapper.go b/pkg/infra/filestorage/wrapper.go index b9718f6a0ed..978777f3e7e 100644 --- a/pkg/infra/filestorage/wrapper.go +++ b/pkg/infra/filestorage/wrapper.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/accesscontrol" ) var ( @@ -17,102 +18,54 @@ var ( ) type wrapper struct { - log log.Logger - wrapped FileStorage - pathFilters *PathFilters - rootFolder string + log log.Logger + wrapped FileStorage + filter PathFilter + rootFolder string } -func addRootFolderToFilters(pathFilters *PathFilters, rootFolder string) *PathFilters { - if pathFilters == nil { - return pathFilters - } - - for i := range pathFilters.disallowedPaths { - pathFilters.disallowedPaths[i] = rootFolder + strings.TrimPrefix(pathFilters.disallowedPaths[i], Delimiter) - } - for i := range pathFilters.disallowedPrefixes { - pathFilters.disallowedPrefixes[i] = rootFolder + strings.TrimPrefix(pathFilters.disallowedPrefixes[i], Delimiter) - } - for i := range pathFilters.allowedPaths { - pathFilters.allowedPaths[i] = rootFolder + strings.TrimPrefix(pathFilters.allowedPaths[i], Delimiter) - } - for i := range pathFilters.allowedPrefixes { - pathFilters.allowedPrefixes[i] = rootFolder + strings.TrimPrefix(pathFilters.allowedPrefixes[i], Delimiter) - } - - return pathFilters +func wrapPathFilter(filter PathFilter, rootFolder string) PathFilter { + return &wrappedPathFilter{filter: filter, rootFolder: rootFolder} } -func copyPathFilters(p *PathFilters) *PathFilters { - if p == nil { - return nil - } - - return NewPathFilters(p.allowedPrefixes, p.allowedPaths, p.disallowedPrefixes, p.disallowedPaths) +type wrappedPathFilter struct { + rootFolder string + filter PathFilter } -func addPathFilters(base *PathFilters, new *PathFilters) *PathFilters { - if new == nil { - return base - } +func (w wrappedPathFilter) IsAllowed(path string) bool { + pathWithReplacedRoot := Delimiter + strings.TrimPrefix(path, w.rootFolder) + return w.filter.IsAllowed(pathWithReplacedRoot) +} - if new.allowedPrefixes != nil { - if base.allowedPrefixes != nil { - base.allowedPrefixes = append(base.allowedPrefixes, new.allowedPrefixes...) - } else { - copiedPrefixes := make([]string, len(new.allowedPrefixes)) - copy(copiedPrefixes, new.allowedPrefixes) - base.allowedPrefixes = copiedPrefixes +func (w wrappedPathFilter) ToString() string { + return w.filter.ToString() +} + +func (w wrappedPathFilter) asSQLFilter() accesscontrol.SQLFilter { + sqlFilter := w.filter.asSQLFilter() + for i := range sqlFilter.Args { + if path, ok := sqlFilter.Args[i].(string); ok { + sqlFilter.Args[i] = w.rootFolder + strings.TrimPrefix(path, Delimiter) } } - if new.allowedPaths != nil { - if base.allowedPaths != nil { - base.allowedPaths = append(base.allowedPaths, new.allowedPaths...) - } else { - copiedPaths := make([]string, len(new.allowedPaths)) - copy(copiedPaths, new.allowedPaths) - base.allowedPaths = copiedPaths - } - } - - if new.disallowedPrefixes != nil { - if base.disallowedPrefixes != nil { - base.disallowedPrefixes = append(base.disallowedPrefixes, new.disallowedPrefixes...) - } else { - copiedPrefixes := make([]string, len(new.disallowedPrefixes)) - copy(copiedPrefixes, new.disallowedPrefixes) - base.disallowedPrefixes = copiedPrefixes - } - } - - if new.disallowedPaths != nil { - if base.disallowedPaths != nil { - base.disallowedPaths = append(base.disallowedPaths, new.disallowedPaths...) - } else { - copiedPaths := make([]string, len(new.disallowedPaths)) - copy(copiedPaths, new.disallowedPaths) - base.disallowedPaths = copiedPaths - } - } - - return base + return sqlFilter } -func newWrapper(log log.Logger, wrapped FileStorage, pathFilters *PathFilters, rootFolder string) FileStorage { - var rootedPathFilters *PathFilters - if pathFilters != nil { - rootedPathFilters = addRootFolderToFilters(copyPathFilters(pathFilters), rootFolder) +func newWrapper(log log.Logger, wrapped FileStorage, pathFilter PathFilter, rootFolder string) FileStorage { + var wrappedPathFilter PathFilter + if pathFilter != nil { + wrappedPathFilter = wrapPathFilter(pathFilter, rootFolder) } else { - rootedPathFilters = allowAllPathFilters() + wrappedPathFilter = wrapPathFilter(NewAllowAllPathFilter(), rootFolder) } return &wrapper{ - log: log, - wrapped: wrapped, - pathFilters: rootedPathFilters, - rootFolder: rootFolder, + log: log, + wrapped: wrapped, + filter: wrappedPathFilter, + rootFolder: rootFolder, } } @@ -198,7 +151,7 @@ func (b wrapper) Get(ctx context.Context, path string) (*File, error) { } rootedPath := b.addRoot(path) - if !b.pathFilters.IsAllowed(rootedPath) { + if !b.filter.IsAllowed(rootedPath) { return nil, nil } @@ -212,13 +165,14 @@ func (b wrapper) Get(ctx context.Context, path string) (*File, error) { } return file, err } + func (b wrapper) Delete(ctx context.Context, path string) error { if err := b.validatePath(path); err != nil { return err } rootedPath := b.addRoot(path) - if !b.pathFilters.IsAllowed(rootedPath) { + if !b.filter.IsAllowed(rootedPath) { return nil } @@ -242,7 +196,7 @@ func (b wrapper) Upsert(ctx context.Context, file *UpsertFileCommand) error { } rootedPath := b.addRoot(file.Path) - if !b.pathFilters.IsAllowed(rootedPath) { + if !b.filter.IsAllowed(rootedPath) { return nil } @@ -284,7 +238,7 @@ func (b wrapper) listOptionsWithDefaults(options *ListOptions) *ListOptions { if options == nil { return &ListOptions{ Recursive: false, - PathFilters: b.pathFilters, + Filter: b.filter, WithFiles: true, WithFolders: false, WithContents: false, @@ -295,20 +249,25 @@ func (b wrapper) listOptionsWithDefaults(options *ListOptions) *ListOptions { if !options.WithFiles && !options.WithFolders { withFiles = true } - if options.PathFilters == nil { + if b.filter == nil { return &ListOptions{ Recursive: options.Recursive, - PathFilters: b.pathFilters, + Filter: b.filter, WithFiles: withFiles, WithFolders: options.WithFolders, WithContents: options.WithContents, } } - rootedFilters := addRootFolderToFilters(copyPathFilters(options.PathFilters), b.rootFolder) + var filter PathFilter + if options.Filter != nil { + filter = newAndPathFilter(b.filter, wrapPathFilter(options.Filter, b.rootFolder)) + } else { + filter = b.filter + } return &ListOptions{ Recursive: options.Recursive, - PathFilters: addPathFilters(rootedFilters, b.pathFilters), + Filter: filter, WithFiles: withFiles, WithFolders: options.WithFolders, WithContents: options.WithContents, @@ -321,7 +280,7 @@ func (b wrapper) CreateFolder(ctx context.Context, path string) error { } rootedPath := b.addRoot(path) - if !b.pathFilters.IsAllowed(rootedPath) { + if !b.filter.IsAllowed(rootedPath) { return nil } @@ -334,7 +293,7 @@ func (b wrapper) DeleteFolder(ctx context.Context, path string) error { } rootedPath := b.addRoot(path) - if !b.pathFilters.IsAllowed(rootedPath) { + if !b.filter.IsAllowed(rootedPath) { return nil } @@ -356,13 +315,6 @@ func (b wrapper) List(ctx context.Context, folderPath string, paging *Paging, op } options = b.listOptionsWithDefaults(options) - if (!options.WithFiles && !options.WithFolders) || options.isDenyAll() { - return &ListResponse{ - Files: []*File{}, - HasMore: false, - LastPath: "", - }, nil - } var fileChan = make(chan *File) fileRetrievalCtx, cancelFileGet := context.WithCancel(ctx) @@ -397,7 +349,6 @@ func (b wrapper) List(ctx context.Context, folderPath string, paging *Paging, op file := <-fileChan if file != nil { - file.FileMetadata.FullPath = b.removeRoot(file.FileMetadata.FullPath) var contents []byte if options.WithContents { contents = file.Contents diff --git a/pkg/infra/filestorage/wrapper_test.go b/pkg/infra/filestorage/wrapper_test.go index aeee9662c12..0fd9d55f085 100644 --- a/pkg/infra/filestorage/wrapper_test.go +++ b/pkg/infra/filestorage/wrapper_test.go @@ -7,157 +7,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestWrapper_addRootFolderToFilters(t *testing.T) { - t.Run("should return null if passed filters are null", func(t *testing.T) { - require.Nil(t, addRootFolderToFilters(nil, "root")) - }) - - t.Run("should not allocate empty arrays in place of nil arrays", func(t *testing.T) { - filters := NewPathFilters(nil, nil, nil, nil) - rootedFilters := addRootFolderToFilters(filters, "root") - require.NotNil(t, rootedFilters) - require.Nil(t, rootedFilters.disallowedPrefixes) - require.Nil(t, rootedFilters.disallowedPaths) - require.Nil(t, rootedFilters.allowedPrefixes) - require.Nil(t, rootedFilters.allowedPaths) - }) - - t.Run("should preserve empty arrays", func(t *testing.T) { - filters := NewPathFilters([]string{}, []string{}, nil, nil) - rootedFilters := addRootFolderToFilters(filters, "root") - require.NotNil(t, rootedFilters) - require.Nil(t, rootedFilters.disallowedPrefixes) - require.Nil(t, rootedFilters.disallowedPaths) - require.NotNil(t, rootedFilters.allowedPrefixes) - require.Equal(t, []string{}, rootedFilters.allowedPrefixes) - - require.NotNil(t, rootedFilters.allowedPaths) - require.Equal(t, []string{}, rootedFilters.allowedPaths) - }) - - t.Run("should mutate arrays rather than reallocate", func(t *testing.T) { - filters := NewPathFilters([]string{"/abc", "/abc2"}, nil, nil, []string{"/abc/", "/abc2/"}) - originalAllowedPrefixes := filters.allowedPrefixes - originalDisallowedPaths := filters.disallowedPaths - - rootedFilters := addRootFolderToFilters(filters, "root/") - require.NotNil(t, rootedFilters) - require.Nil(t, rootedFilters.allowedPaths) - require.Nil(t, rootedFilters.disallowedPrefixes) - - expectedAllowedPrefixes := []string{"root/abc", "root/abc2"} - expectedDisallowedPaths := []string{"root/abc/", "root/abc2/"} - require.Equal(t, expectedAllowedPrefixes, rootedFilters.allowedPrefixes) - require.Equal(t, expectedDisallowedPaths, rootedFilters.disallowedPaths) - - require.Equal(t, expectedAllowedPrefixes, originalAllowedPrefixes) - require.Equal(t, expectedDisallowedPaths, originalDisallowedPaths) - }) -} - -func TestWrapper_copyPathFilters(t *testing.T) { - t.Run("should return null if passed pathFilters are null", func(t *testing.T) { - require.Nil(t, copyPathFilters(nil)) - }) - - t.Run("should not allocate empty arrays in place of nil arrays", func(t *testing.T) { - copiedFilters := copyPathFilters(NewPathFilters(nil, nil, nil, nil)) - require.NotNil(t, copiedFilters) - require.Nil(t, copiedFilters.disallowedPrefixes) - require.Nil(t, copiedFilters.disallowedPaths) - require.Nil(t, copiedFilters.allowedPrefixes) - require.Nil(t, copiedFilters.allowedPaths) - }) - - t.Run("should preserve empty arrays", func(t *testing.T) { - copiedFilters := copyPathFilters(NewPathFilters([]string{}, []string{}, nil, nil)) - require.NotNil(t, copiedFilters) - require.Nil(t, copiedFilters.disallowedPrefixes) - require.Nil(t, copiedFilters.disallowedPaths) - require.NotNil(t, copiedFilters.allowedPrefixes) - require.Equal(t, []string{}, copiedFilters.allowedPrefixes) - - require.NotNil(t, copiedFilters.allowedPaths) - require.Equal(t, []string{}, copiedFilters.allowedPaths) - }) - - t.Run("should new pointer with new slices", func(t *testing.T) { - filters := NewPathFilters([]string{"/abc", "/abc2"}, nil, nil, []string{"/abc/", "/abc2/"}) - - copiedFilters := copyPathFilters(filters) - - require.NotSame(t, filters, copiedFilters) - - require.Equal(t, filters.allowedPrefixes, copiedFilters.allowedPrefixes) - require.Equal(t, filters.allowedPaths, copiedFilters.allowedPaths) - require.Equal(t, filters.disallowedPrefixes, copiedFilters.disallowedPrefixes) - require.Equal(t, filters.disallowedPaths, copiedFilters.disallowedPaths) - - copiedFilters.disallowedPaths[0] = "changed" - require.Equal(t, []string{"/abc/", "/abc2/"}, filters.disallowedPaths) - require.Equal(t, []string{"changed", "/abc2/"}, copiedFilters.disallowedPaths) - require.NotEqual(t, filters.disallowedPaths, copiedFilters.disallowedPaths) - }) -} - -func TestWrapper_addPathFilters(t *testing.T) { - t.Run("should return pointer to the first argument", func(t *testing.T) { - base := NewPathFilters(nil, nil, nil, nil) - toAdd := NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}) - require.Same(t, base, addPathFilters(base, toAdd)) - }) - - testcases := []struct { - base *PathFilters - toAdd *PathFilters - expected *PathFilters - }{ - { - base: NewPathFilters(nil, nil, nil, nil), - toAdd: NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}), - expected: NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}), - }, - { - base: NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}), - toAdd: NewPathFilters(nil, nil, nil, nil), - expected: NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}), - }, - { - base: NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}), - toAdd: NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}), - expected: NewPathFilters([]string{"abc", "abc"}, []string{"abc2", "abc2"}, []string{"abc3", "abc3"}, []string{"abc4", "abc4"}), - }, - { - base: NewPathFilters([]string{"abc"}, []string{}, nil, []string{"abc4"}), - toAdd: NewPathFilters([]string{"abc"}, []string{"abc2", "abc22", "abc222"}, []string{"abc3"}, []string{"abc4"}), - expected: NewPathFilters([]string{"abc", "abc"}, []string{"abc2", "abc22", "abc222"}, []string{"abc3"}, []string{"abc4", "abc4"}), - }, - } - - for _, tt := range testcases { - require.Equal(t, tt.expected, addPathFilters(tt.base, tt.toAdd)) - } - - t.Run("should not reuse arrays allocations from the second arg", func(t *testing.T) { - base := NewPathFilters(nil, []string{}, nil, nil) - toAdd := NewPathFilters([]string{"abc"}, []string{"abc2"}, []string{"abc3"}, []string{"abc4"}) - - _ = addPathFilters(base, toAdd) - - require.Equal(t, toAdd.allowedPaths, base.allowedPaths) - base.allowedPaths[0] = "mutated" - require.Equal(t, []string{"mutated"}, base.allowedPaths) - require.Equal(t, []string{"abc2"}, toAdd.allowedPaths) - require.NotEqual(t, toAdd.allowedPaths, base.allowedPaths) - - require.Equal(t, toAdd.allowedPrefixes, base.allowedPrefixes) - base.allowedPrefixes[0] = "mutated2" - require.Equal(t, []string{"mutated2"}, base.allowedPrefixes) - require.Equal(t, []string{"abc"}, toAdd.allowedPrefixes) - require.NotEqual(t, toAdd.allowedPrefixes, base.allowedPrefixes) - }) -} - func TestFilestorage_getParentFolderPath(t *testing.T) { var tests = []struct { name string diff --git a/pkg/infra/usagestats/statscollector/prometheus_flavor.go b/pkg/infra/usagestats/statscollector/prometheus_flavor.go new file mode 100644 index 00000000000..5d79e9f2a2f --- /dev/null +++ b/pkg/infra/usagestats/statscollector/prometheus_flavor.go @@ -0,0 +1,119 @@ +package statscollector + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "time" + + "github.com/grafana/grafana/pkg/models" +) + +const promFlavorCacheLifetime = time.Hour + +type memoPrometheusFlavor struct { + variants map[string]int64 + + memoized time.Time +} + +func (s *Service) detectPrometheusVariants(ctx context.Context) (map[string]int64, error) { + if s.promFlavorCache.memoized.Add(promFlavorCacheLifetime).After(time.Now()) && + s.promFlavorCache.variants != nil { + return s.promFlavorCache.variants, nil + } + + dsProm := &models.GetDataSourcesByTypeQuery{Type: "prometheus"} + err := s.datasources.GetDataSourcesByType(ctx, dsProm) + if err != nil { + s.log.Error("Failed to read all Prometheus data sources", "error", err) + return nil, err + } + + variants := map[string]int64{} + for _, ds := range dsProm.Result { + variant, err := s.detectPrometheusVariant(ctx, ds) + if err != nil { + return nil, err + } + if variant == "" { + continue + } + + if _, exists := variants[variant]; !exists { + variants[variant] = 0 + } + variants[variant] += 1 + } + + s.promFlavorCache.variants = variants + s.promFlavorCache.memoized = time.Now() + return variants, nil +} + +func (s *Service) detectPrometheusVariant(ctx context.Context, ds *models.DataSource) (string, error) { + type buildInfo struct { + Data struct { + Application *string `json:"application"` + Features map[string]interface{} `json:"features"` + } `json:"data"` + } + + c, err := s.datasources.GetHTTPTransport(ds, s.httpClientProvider) + if err != nil { + s.log.Error("Failed to get HTTP client for Prometheus data source", "error", err) + return "", err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ds.Url+"/api/v1/status/buildinfo", nil) + if err != nil { + s.log.Error("Failed to create Prometheus build info request", "error", err) + return "", err + } + + resp, err := c.RoundTrip(req) + if err != nil { + // Possibly configuration error, the risk of a false positive is + // too high. + s.log.Debug("Failed to send Prometheus build info request", "error", err) + return "", nil + } + defer func() { + err := resp.Body.Close() + if err != nil { + s.log.Error("Got error while closing response body") + } + }() + + if resp.StatusCode == 404 { + return "cortex-like", nil + } + + if resp.StatusCode != 200 { + return "unknown", nil + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + s.log.Error("Failed to read Prometheus build info", "error", err) + return "", err + } + + bi := &buildInfo{} + err = json.Unmarshal(body, bi) + if err != nil { + s.log.Warn("Failed to read Prometheus build info JSON", "error", err) + return "", err + } + + if bi.Data.Application != nil && *bi.Data.Application == "Grafana Mimir" { + return "mimir", nil + } + + if bi.Data.Features != nil { + return "mimir-like", nil + } + + return "vanilla", nil +} diff --git a/pkg/infra/usagestats/statscollector/prometheus_flavor_test.go b/pkg/infra/usagestats/statscollector/prometheus_flavor_test.go new file mode 100644 index 00000000000..60c41091ac1 --- /dev/null +++ b/pkg/infra/usagestats/statscollector/prometheus_flavor_test.go @@ -0,0 +1,86 @@ +package statscollector + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" + "github.com/grafana/grafana/pkg/setting" +) + +func TestDetectPrometheusVariant(t *testing.T) { + vanilla := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, `{"status":"success","data":{"version":"","revision":"","branch":"","buildUser":"","buildDate":"","goVersion":"go1.17.6"}}`) + })) + t.Cleanup(vanilla.Close) + + mimir := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, `{"status":"success","data":{"application":"Grafana Mimir","version":"2.0.0","revision":"9fd2da5","branch":"HEAD","goVersion":"go1.17.8","features":{"ruler_config_api":"true","alertmanager_config_api":"true","query_sharding":"false","federated_rules":"false"}}}`) + })) + t.Cleanup(mimir.Close) + + cortex := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + t.Cleanup(cortex.Close) + + sqlStore := mockstore.NewSQLStoreMock() + s := createService( + t, + setting.NewCfg(), + sqlStore, + withDatasources(mockDatasourceService{datasources: []*models.DataSource{ + { + Id: 1, + OrgId: 1, + Version: 1, + Name: "Vanilla", + Type: "prometheus", + Access: "proxy", + Url: vanilla.URL, + }, + { + Id: 2, + OrgId: 1, + Version: 1, + Name: "Mimir", + Type: "prometheus", + Access: "proxy", + Url: mimir.URL, + }, + { + Id: 3, + OrgId: 1, + Version: 1, + Name: "Another Mimir", + Type: "prometheus", + Access: "proxy", + Url: mimir.URL, + }, + { + Id: 4, + OrgId: 1, + Version: 1, + Name: "Cortex", + Type: "prometheus", + Access: "proxy", + Url: cortex.URL, + }, + }}), + ) + + flavors, err := s.detectPrometheusVariants(context.Background()) + require.NoError(t, err) + + assert.Equal(t, int64(2), flavors["mimir"]) + assert.Equal(t, int64(1), flavors["vanilla"]) + assert.Equal(t, int64(1), flavors["cortex-like"]) +} diff --git a/pkg/infra/usagestats/statscollector/service.go b/pkg/infra/usagestats/statscollector/service.go index b96abfb4e68..1f9a7a752d7 100644 --- a/pkg/infra/usagestats/statscollector/service.go +++ b/pkg/infra/usagestats/statscollector/service.go @@ -6,31 +6,34 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/services/featuremgmt" - - "github.com/grafana/grafana/pkg/infra/usagestats" - + "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" ) type Service struct { - cfg *setting.Cfg - sqlstore sqlstore.Store - plugins plugins.Store - social social.Service - usageStats usagestats.Service - features *featuremgmt.FeatureManager + cfg *setting.Cfg + sqlstore sqlstore.Store + plugins plugins.Store + social social.Service + usageStats usagestats.Service + features *featuremgmt.FeatureManager + datasources datasources.DataSourceService + httpClientProvider httpclient.Provider log log.Logger startTime time.Time concurrentUserStatsCache memoConcurrentUserStats + promFlavorCache memoPrometheusFlavor } func ProvideService( @@ -40,14 +43,18 @@ func ProvideService( social social.Service, plugins plugins.Store, features *featuremgmt.FeatureManager, + datasourceService datasources.DataSourceService, + httpClientProvider httpclient.Provider, ) *Service { s := &Service{ - cfg: cfg, - sqlstore: store, - plugins: plugins, - social: social, - usageStats: usagestats, - features: features, + cfg: cfg, + sqlstore: store, + plugins: plugins, + social: social, + usageStats: usagestats, + features: features, + datasources: datasourceService, + httpClientProvider: httpClientProvider, startTime: time.Now(), log: log.New("infra.usagestats.collector"), @@ -189,6 +196,15 @@ func (s *Service) collect(ctx context.Context) (map[string]interface{}, error) { return nil, err } + variants, err := s.detectPrometheusVariants(ctx) + if err != nil { + return nil, err + } + + for variant, count := range variants { + m["stats.ds.prometheus.flavor."+variant+".count"] = count + } + // send access counters for each data source // but ignore any custom data sources // as sending that name could be sensitive information diff --git a/pkg/infra/usagestats/statscollector/service_test.go b/pkg/infra/usagestats/statscollector/service_test.go index 6ff76c947bd..91419ac5cbc 100644 --- a/pkg/infra/usagestats/statscollector/service_test.go +++ b/pkg/infra/usagestats/statscollector/service_test.go @@ -3,10 +3,13 @@ package statscollector import ( "context" "fmt" + "net/http" "testing" "time" - "github.com/grafana/grafana/pkg/services/featuremgmt" + sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + + "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,6 +19,8 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" @@ -348,9 +353,15 @@ func (pr fakePluginStore) Plugins(_ context.Context, pluginTypes ...plugins.Type return result } -func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store) *Service { +func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store, opts ...func(*serviceOptions)) *Service { t.Helper() + o := &serviceOptions{datasources: mockDatasourceService{}} + + for _, opt := range opts { + opt(o) + } + return ProvideService( &usagestats.UsageStatsMock{}, cfg, @@ -358,5 +369,32 @@ func createService(t testing.TB, cfg *setting.Cfg, store sqlstore.Store) *Servic &mockSocial{}, &fakePluginStore{}, featuremgmt.WithFeatures("feature1", "feature2"), + o.datasources, + httpclient.NewProvider(), ) } + +type serviceOptions struct { + datasources datasources.DataSourceService +} + +func withDatasources(ds datasources.DataSourceService) func(*serviceOptions) { + return func(options *serviceOptions) { + options.datasources = ds + } +} + +type mockDatasourceService struct { + datasources.DataSourceService + + datasources []*models.DataSource +} + +func (s mockDatasourceService) GetDataSourcesByType(ctx context.Context, query *models.GetDataSourcesByTypeQuery) error { + query.Result = s.datasources + return nil +} + +func (s mockDatasourceService) GetHTTPTransport(ds *models.DataSource, provider httpclient.Provider, customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) { + return provider.GetTransport() +} diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index a3b2a2f2e89..a3b95627640 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -207,7 +207,7 @@ func OrgAdminFolderAdminOrTeamAdmin(ss sqlstore.Store) func(c *models.ReqContext } isAdminOfTeamsQuery := models.IsAdminOfTeamsQuery{SignedInUser: c.SignedInUser} - if err := sqlstore.IsAdminOfTeams(c.Req.Context(), &isAdminOfTeamsQuery); err != nil { + if err := ss.IsAdminOfTeams(c.Req.Context(), &isAdminOfTeamsQuery); err != nil { c.JsonApiErr(500, "Failed to check if user is a team admin", err) } diff --git a/pkg/models/apikey.go b/pkg/models/apikey.go index 430dddf311f..aabbc4e78b4 100644 --- a/pkg/models/apikey.go +++ b/pkg/models/apikey.go @@ -26,6 +26,7 @@ type ApiKey struct { // --------------------- // COMMANDS +// swagger:model type AddApiKeyCommand struct { Name string `json:"name" binding:"Required"` Role RoleType `json:"role" binding:"Required"` diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index d2f0f732dd4..f91c28f0b58 100644 --- a/pkg/models/org_user.go +++ b/pkg/models/org_user.go @@ -14,6 +14,7 @@ var ( ErrOrgUserAlreadyAdded = errors.New("user is already added to organization") ) +// swagger:enum RoleType type RoleType string const ( diff --git a/pkg/server/wire.go b/pkg/server/wire.go index a1cbc424382..dfcf0565de0 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -239,7 +239,6 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(alerting.DashAlertExtractor), new(*alerting.DashAlertExtractorService)), comments.ProvideService, guardian.ProvideService, - prefimpl.ProvideService, avatar.ProvideAvatarCacheServer, authproxy.ProvideAuthProxy, statscollector.ProvideService, @@ -253,6 +252,7 @@ var wireSet = wire.NewSet( wireBasicSet, sqlstore.ProvideService, wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)), + wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), ngmetrics.ProvideService, wire.Bind(new(notifications.TempUserStore), new(*sqlstore.SQLStore)), wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), @@ -260,6 +260,7 @@ var wireSet = wire.NewSet( wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(sqlstore.Store), new(*sqlstore.SQLStore)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), + prefimpl.ProvideService, ) var wireTestSet = wire.NewSet( @@ -268,6 +269,7 @@ var wireTestSet = wire.NewSet( sqlstore.ProvideServiceForTests, ngmetrics.ProvideServiceForTest, wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)), + wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), notifications.MockNotificationService, wire.Bind(new(notifications.TempUserStore), new(*mockstore.SQLStoreMock)), @@ -276,6 +278,8 @@ var wireTestSet = wire.NewSet( wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationServiceMock)), mockstore.NewSQLStoreMock, wire.Bind(new(sqlstore.Store), new(*sqlstore.SQLStore)), + wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), + prefimpl.ProvideService, ) func Initialize(cla setting.CommandLineArgs, opts Options, apiOpts api.ServerOptions) (*Server, error) { diff --git a/pkg/services/accesscontrol/database/resource_permissions.go b/pkg/services/accesscontrol/database/resource_permissions.go index f54c9584bd9..12e564886bb 100644 --- a/pkg/services/accesscontrol/database/resource_permissions.go +++ b/pkg/services/accesscontrol/database/resource_permissions.go @@ -32,7 +32,7 @@ type flatResourcePermission struct { } func (p *flatResourcePermission) IsManaged() bool { - return strings.HasPrefix(p.RoleName, "managed:") && !p.IsInherited() + return strings.HasPrefix(p.RoleName, accesscontrol.ManagedRolePrefix) && !p.IsInherited() } func (p *flatResourcePermission) IsInherited() bool { diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index 0d4a2fc6f36..f3036c9710b 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -41,6 +41,10 @@ func (r *Role) IsFixed() bool { return strings.HasPrefix(r.Name, FixedRolePrefix) } +func (r *Role) IsBasic() bool { + return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix) +} + func (r *Role) GetDisplayName() string { if r.IsFixed() && r.DisplayName == "" { r.DisplayName = fallbackDisplayName(r.Name) @@ -118,6 +122,10 @@ func (r *RoleDTO) IsFixed() bool { return strings.HasPrefix(r.Name, FixedRolePrefix) } +func (r *RoleDTO) IsBasic() bool { + return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix) +} + func (r *RoleDTO) GetDisplayName() string { if r.IsFixed() && r.DisplayName == "" { r.DisplayName = fallbackDisplayName(r.Name) @@ -261,9 +269,12 @@ type SetResourcePermissionCommand struct { } const ( - GlobalOrgID = 0 - FixedRolePrefix = "fixed:" - RoleGrafanaAdmin = "Grafana Admin" + GlobalOrgID = 0 + FixedRolePrefix = "fixed:" + ManagedRolePrefix = "managed:" + BasicRolePrefix = "grafana:builtins:" + BasicRoleUIDPrefix = "grafana_builtins_" + RoleGrafanaAdmin = "Grafana Admin" GeneralFolderUID = "general" diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 7fda9c47e85..59add0c5eb2 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -7,6 +7,7 @@ import ( "time" "github.com/benbjohnson/clock" + "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" "golang.org/x/sync/errgroup" @@ -14,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/alerting/metrics" "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/rendering" @@ -77,7 +79,6 @@ func ProvideAlertEngine(renderer rendering.Service, requestValidator models.Plug sqlStore: sqlStore, dashAlertExtractor: dashAlertExtractor, } - e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1) e.execQueue = make(chan *Job, 1000) e.scheduler = newScheduler() e.evalHandler = NewEvalHandler(e.DataService) @@ -92,6 +93,7 @@ func ProvideAlertEngine(renderer rendering.Service, requestValidator models.Plug // Run starts the alerting service background process. func (e *AlertEngine) Run(ctx context.Context) error { + e.ticker = NewTicker(clock.New(), 1*time.Second, metrics.NewTickerMetrics(prometheus.DefaultRegisterer)) alertGroup, ctx := errgroup.WithContext(ctx) alertGroup.Go(func() error { return e.alertingTicker(ctx) }) alertGroup.Go(func() error { return e.runJobDispatcher(ctx) }) diff --git a/pkg/services/alerting/metrics/metrics.go b/pkg/services/alerting/metrics/metrics.go new file mode 100644 index 00000000000..e93087aa8dd --- /dev/null +++ b/pkg/services/alerting/metrics/metrics.go @@ -0,0 +1,35 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +type Ticker struct { + LastTickTime prometheus.Gauge + NextTickTime prometheus.Gauge + IntervalSeconds prometheus.Gauge +} + +func NewTickerMetrics(reg prometheus.Registerer) *Ticker { + return &Ticker{ + LastTickTime: promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Namespace: "grafana", + Subsystem: "alerting", + Name: "ticker_last_consumed_tick_timestamp_seconds", + Help: "Timestamp of the last consumed tick in seconds.", + }), + NextTickTime: promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Namespace: "grafana", + Subsystem: "alerting", + Name: "ticker_next_tick_timestamp_seconds", + Help: "Timestamp of the next tick in seconds before it is consumed.", + }), + IntervalSeconds: promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Namespace: "grafana", + Subsystem: "alerting", + Name: "ticker_interval_seconds", + Help: "Interval at which the ticker is meant to tick.", + }), + } +} diff --git a/pkg/services/alerting/ticker.go b/pkg/services/alerting/ticker.go index 48df4c227d5..16ea19ea5a3 100644 --- a/pkg/services/alerting/ticker.go +++ b/pkg/services/alerting/ticker.go @@ -1,76 +1,57 @@ package alerting import ( + "fmt" "time" "github.com/benbjohnson/clock" + + "github.com/grafana/grafana/pkg/services/alerting/metrics" ) // Ticker is a ticker to power the alerting scheduler. it's like a time.Ticker, except: // * it doesn't drop ticks for slow receivers, rather, it queues up. so that callers are in control to instrument what's going on. -// * it automatically ticks every second, which is the right thing in our current design -// * it ticks on intervalSec marks or very shortly after. this provides a predictable load pattern +// * it ticks on interval marks or very shortly after. this provides a predictable load pattern // (this shouldn't cause too much load contention issues because the next steps in the pipeline just process at their own pace) // * the timestamps are used to mark "last datapoint to query for" and as such, are a configurable amount of seconds in the past -// * because we want to allow: -// - a clean "resume where we left off" and "don't yield ticks we already did" -// - adjusting offset over time to compensate for storage backing up or getting fast and providing lower latency -// you specify a lastProcessed timestamp as well as an offset at creation, or runtime type Ticker struct { - C chan time.Time - clock clock.Clock - last time.Time - offset time.Duration - newOffset chan time.Duration - intervalSec int64 - paused bool + C chan time.Time + clock clock.Clock + last time.Time + interval time.Duration + metrics *metrics.Ticker } -// NewTicker returns a ticker that ticks on intervalSec marks or very shortly after, and never drops ticks -func NewTicker(last time.Time, initialOffset time.Duration, c clock.Clock, intervalSec int64) *Ticker { - t := &Ticker{ - C: make(chan time.Time), - clock: c, - last: last, - offset: initialOffset, - newOffset: make(chan time.Duration), - intervalSec: intervalSec, +// NewTicker returns a Ticker that ticks on interval marks (or very shortly after) starting at c.Now(), and never drops ticks. interval should not be negative or zero. +func NewTicker(c clock.Clock, interval time.Duration, metric *metrics.Ticker) *Ticker { + if interval <= 0 { + panic(fmt.Errorf("non-positive interval [%v] is not allowed", interval)) } + t := &Ticker{ + C: make(chan time.Time), + clock: c, + last: c.Now(), + interval: interval, + metrics: metric, + } + metric.IntervalSeconds.Set(t.interval.Seconds()) // Seconds report fractional part as well, so it matches the format of the timestamp we report below go t.run() return t } func (t *Ticker) run() { for { - next := t.last.Add(time.Duration(t.intervalSec) * time.Second) - diff := t.clock.Now().Add(-t.offset).Sub(next) + next := t.last.Add(t.interval) // calculate the time of the next tick + t.metrics.NextTickTime.Set(float64(next.UnixNano()) / 1e9) + diff := t.clock.Now().Sub(next) // calculate the difference between the current time and the next tick + // if difference is not negative, then it should tick if diff >= 0 { - if !t.paused { - t.C <- next - } + t.C <- next t.last = next + t.metrics.LastTickTime.Set(float64(next.UnixNano()) / 1e9) continue } // tick is too young. try again when ... - select { - case <-t.clock.After(-diff): // ...it'll definitely be old enough - case offset := <-t.newOffset: // ...it might be old enough - t.offset = offset - } + <-t.clock.After(-diff) // ...it'll definitely be old enough } } - -// ResetOffset resets the offset. -func (t *Ticker) ResetOffset(duration time.Duration) { - t.newOffset <- duration -} - -// Pause unpauses the ticker and no ticks will be sent. -func (t *Ticker) Pause() { - t.paused = true -} - -// Unpause unpauses the ticker and ticks will be sent. -func (t *Ticker) Unpause() { - t.paused = false -} diff --git a/pkg/services/alerting/ticker_test.go b/pkg/services/alerting/ticker_test.go index bb679750a51..8eb4061e14d 100644 --- a/pkg/services/alerting/ticker_test.go +++ b/pkg/services/alerting/ticker_test.go @@ -1,121 +1,152 @@ package alerting -// import ( -// "testing" -// "time" -// -// "github.com/benbjohnson/clock" -// ) -// -// func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) { -// if !tick.Equal(last.Add(time.Duration(1) * time.Second)) { -// t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick) -// } -// } -// -// returns the new last tick seen -// func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time { -// for { -// select { -// case tick := <-ticker.C: -// inspectTick(tick, last, offset, t) -// last = tick -// case <-time.NewTimer(wait).C: -// if last.Before(desiredLast) { -// t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last) -// } -// if last.After(desiredLast) { -// t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last) -// } -// return last -// } -// } -// } -// -// func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) { -// for { -// select { -// case tick := <-ticker.C: -// t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick) -// case <-time.NewTimer(wait).C: -// return -// } -// } -// } -// -// func TestTickerRetro1Hour(t *testing.T) { -// offset := time.Duration(10) * time.Second -// last := time.Unix(0, 0) -// mock := clock.NewMock() -// mock.Add(time.Duration(1) * time.Hour) -// desiredLast := mock.Now().Add(-offset) -// ticker := NewTicker(last, offset, mock) -// -// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t) -// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t) -// -// } -// -// func TestAdvanceWithUpdateOffset(t *testing.T) { -// offset := time.Duration(10) * time.Second -// last := time.Unix(0, 0) -// mock := clock.NewMock() -// mock.Add(time.Duration(1) * time.Hour) -// desiredLast := mock.Now().Add(-offset) -// ticker := NewTicker(last, offset, mock) -// -// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t) -// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t) -// -// // lowering offset should see a few more ticks -// offset = time.Duration(5) * time.Second -// ticker.updateOffset(offset) -// desiredLast = mock.Now().Add(-offset) -// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t) -// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t) -// -// // advancing clock should see even more ticks -// mock.Add(time.Duration(1) * time.Hour) -// desiredLast = mock.Now().Add(-offset) -// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t) -// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t) -// -// } -// -// func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) { -// last := time.Unix(int64(lastSeconds), 0) -// offset := time.Duration(offsetSeconds) * time.Second -// return last, offset -// } -// -// func TestTickerNoAdvance(t *testing.T) { -// -// // it's 00:01:00 now. what are some cases where we don't want the ticker to advance? -// mock := clock.NewMock() -// mock.Add(time.Duration(60) * time.Second) -// -// type Case struct { -// last int -// offset int -// } -// -// // note that some cases add up to now, others go into the future -// cases := []Case{ -// {50, 10}, -// {50, 30}, -// {59, 1}, -// {59, 10}, -// {59, 30}, -// {60, 1}, -// {60, 10}, -// {60, 30}, -// {90, 1}, -// {90, 10}, -// {90, 30}, -// } -// for _, c := range cases { -// last, offset := getCase(c.last, c.offset) -// ticker := NewTicker(last, offset, mock) -// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t) -// } -// } +import ( + "bytes" + "context" + "fmt" + "math/rand" + "sync" + "testing" + "time" + + "github.com/benbjohnson/clock" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/alerting/metrics" +) + +func TestTicker(t *testing.T) { + readChanOrFail := func(t *testing.T, ctx context.Context, c chan time.Time) time.Time { + t.Helper() + select { + case tick := <-c: + return tick + case <-ctx.Done(): + require.Failf(t, fmt.Sprintf("%v", ctx.Err()), "timeout reading the channel") + default: + require.Failf(t, "channel is empty but it should have a tick", "") + } + return time.Time{} + } + t.Run("should not drop ticks", func(t *testing.T) { + clk := clock.NewMock() + interval := time.Duration(rand.Int63n(100)+10) * time.Second + ticker := NewTicker(clk, interval, metrics.NewTickerMetrics(prometheus.NewRegistry())) + + ticks := rand.Intn(9) + 1 + jitter := rand.Int63n(int64(interval) - 1) + + clk.Add(time.Duration(ticks)*interval + time.Duration(jitter)) + + w := sync.WaitGroup{} + w.Add(1) + regTicks := make([]time.Time, 0, ticks) + go func() { + for timestamp := range ticker.C { + regTicks = append(regTicks, timestamp) + if len(regTicks) == ticks { + w.Done() + } + } + }() + w.Wait() + + require.Len(t, regTicks, ticks) + + t.Run("ticks should monotonically increase", func(t *testing.T) { + for i := 1; i < len(regTicks); i++ { + previous := regTicks[i-1] + current := regTicks[i] + require.Equal(t, interval, current.Sub(previous)) + } + }) + }) + + t.Run("should not put anything to channel until it's time", func(t *testing.T) { + clk := clock.NewMock() + interval := time.Duration(rand.Int63n(9)+1) * time.Second + ticker := NewTicker(clk, interval, metrics.NewTickerMetrics(prometheus.NewRegistry())) + expectedTick := clk.Now().Add(interval) + for { + require.Empty(t, ticker.C) + clk.Add(time.Duration(rand.Int31n(500)+100) * time.Millisecond) + if clk.Now().After(expectedTick) { + break + } + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + t.Cleanup(func() { + cancel() + }) + actual := readChanOrFail(t, ctx, ticker.C) + require.Equal(t, expectedTick, actual) + }) + + t.Run("should put the tick in the channel immediately if it is behind", func(t *testing.T) { + clk := clock.NewMock() + interval := time.Duration(rand.Int63n(9)+1) * time.Second + ticker := NewTicker(clk, interval, metrics.NewTickerMetrics(prometheus.NewRegistry())) + + // We can expect the first tick to be at a consistent interval. Take a snapshot of the clock now, before we advance it. + expectedTick := clk.Now().Add(interval) + + require.Empty(t, ticker.C) + + clk.Add(interval) // advance the clock by the interval to make the ticker tick the first time. + clk.Add(interval) // advance the clock by the interval to make the ticker tick the second time. + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + t.Cleanup(func() { + cancel() + }) + + // Irregardless of wall time, the first tick should be initial clock + interval. + actual1 := readChanOrFail(t, ctx, ticker.C) + require.Equal(t, expectedTick, actual1) + + var actual2 time.Time + require.Eventually(t, func() bool { + actual2 = readChanOrFail(t, ctx, ticker.C) + return true + }, time.Second, 10*time.Millisecond) + + // Similarly, the second tick should be last tick + interval irregardless of wall time. + require.Equal(t, expectedTick.Add(interval), actual2) + }) + + t.Run("should report metrics", func(t *testing.T) { + clk := clock.NewMock() + clk.Set(time.Now()) + interval := time.Duration(rand.Int63n(9)+1) * time.Second + registry := prometheus.NewPedanticRegistry() + ticker := NewTicker(clk, interval, metrics.NewTickerMetrics(registry)) + expectedTick := clk.Now().Add(interval) + + expectedMetricFmt := `# HELP grafana_alerting_ticker_interval_seconds Interval at which the ticker is meant to tick. + # TYPE grafana_alerting_ticker_interval_seconds gauge + grafana_alerting_ticker_interval_seconds %v + # HELP grafana_alerting_ticker_last_consumed_tick_timestamp_seconds Timestamp of the last consumed tick in seconds. + # TYPE grafana_alerting_ticker_last_consumed_tick_timestamp_seconds gauge + grafana_alerting_ticker_last_consumed_tick_timestamp_seconds %v + # HELP grafana_alerting_ticker_next_tick_timestamp_seconds Timestamp of the next tick in seconds before it is consumed. + # TYPE grafana_alerting_ticker_next_tick_timestamp_seconds gauge + grafana_alerting_ticker_next_tick_timestamp_seconds %v + ` + + expectedMetric := fmt.Sprintf(expectedMetricFmt, interval.Seconds(), 0, float64(expectedTick.UnixNano())/1e9) + + require.NoError(t, testutil.GatherAndCompare(registry, bytes.NewBufferString(expectedMetric), "grafana_alerting_ticker_last_consumed_tick_timestamp_seconds", "grafana_alerting_ticker_next_tick_timestamp_seconds", "grafana_alerting_ticker_interval_seconds")) + + clk.Add(interval) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + t.Cleanup(func() { + cancel() + }) + actual := readChanOrFail(t, ctx, ticker.C) + + expectedMetric = fmt.Sprintf(expectedMetricFmt, interval.Seconds(), float64(actual.UnixNano())/1e9, float64(expectedTick.Add(interval).UnixNano())/1e9) + require.NoError(t, testutil.GatherAndCompare(registry, bytes.NewBufferString(expectedMetric), "grafana_alerting_ticker_last_consumed_tick_timestamp_seconds", "grafana_alerting_ticker_next_tick_timestamp_seconds", "grafana_alerting_ticker_interval_seconds")) + }) +} diff --git a/pkg/services/comments/commentmodel/permissions.go b/pkg/services/comments/commentmodel/permissions.go index b1bcbee82d1..33c17b3eca4 100644 --- a/pkg/services/comments/commentmodel/permissions.go +++ b/pkg/services/comments/commentmodel/permissions.go @@ -105,7 +105,7 @@ func (c *PermissionChecker) CheckWritePermissions(ctx context.Context, orgId int if !c.features.IsEnabled(featuremgmt.FlagAnnotationComments) { return false, nil } - if c.features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !c.accessControl.IsDisabled() { evaluator := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsWrite, accesscontrol.ScopeAnnotationsTypeDashboard) if canEdit, err := c.accessControl.Evaluate(ctx, signedInUser, evaluator); err != nil || !canEdit { return canEdit, err diff --git a/pkg/services/comments/sql_storage.go b/pkg/services/comments/sql_storage.go index 6a95c225912..f9c93702e9e 100644 --- a/pkg/services/comments/sql_storage.go +++ b/pkg/services/comments/sql_storage.go @@ -35,12 +35,11 @@ func (s *sqlStorage) Create(ctx context.Context, orgID int64, objectType string, var result *commentmodel.Comment return result, s.sql.WithTransactionalDbSession(ctx, func(dbSession *sqlstore.DBSession) error { - group := commentmodel.CommentGroup{ - OrgId: orgID, - ObjectType: objectType, - ObjectId: objectID, - } - has, err := dbSession.Get(&group) + var group commentmodel.CommentGroup + has, err := dbSession.NoAutoCondition().Where( + "org_id=? AND object_type=? AND object_id=?", + orgID, objectType, objectID, + ).Get(&group) if err != nil { return err } @@ -49,6 +48,9 @@ func (s *sqlStorage) Create(ctx context.Context, orgID int64, objectType string, groupID := group.Id if !has { + group.OrgId = orgID + group.ObjectType = objectType + group.ObjectId = objectID group.Created = nowUnix group.Updated = nowUnix group.Settings = commentmodel.Settings{} @@ -95,12 +97,11 @@ func (s *sqlStorage) Get(ctx context.Context, orgID int64, objectType string, ob } return result, s.sql.WithTransactionalDbSession(ctx, func(dbSession *sqlstore.DBSession) error { - group := commentmodel.CommentGroup{ - OrgId: orgID, - ObjectType: objectType, - ObjectId: objectID, - } - has, err := dbSession.Get(&group) + var group commentmodel.CommentGroup + has, err := dbSession.NoAutoCondition().Where( + "org_id=? AND object_type=? AND object_id=?", + orgID, objectType, objectID, + ).Get(&group) if err != nil { return err } diff --git a/pkg/services/dashboardsnapshots/dashboardsnapshots.go b/pkg/services/dashboardsnapshots/dashboardsnapshots.go index 51587cfa2dd..001bf4354cd 100644 --- a/pkg/services/dashboardsnapshots/dashboardsnapshots.go +++ b/pkg/services/dashboardsnapshots/dashboardsnapshots.go @@ -40,7 +40,7 @@ func (s *Service) CreateDashboardSnapshot(ctx context.Context, cmd *models.Creat } func (s *Service) GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error { - err := s.SQLStore.GetDashboardSnapshot(query) + err := s.SQLStore.GetDashboardSnapshot(ctx, query) if err != nil { return err } @@ -66,8 +66,8 @@ func (s *Service) DeleteDashboardSnapshot(ctx context.Context, cmd *models.Delet return s.SQLStore.DeleteDashboardSnapshot(ctx, cmd) } -func (s *Service) SearchDashboardSnapshots(_ context.Context, query *models.GetDashboardSnapshotsQuery) error { - return s.SQLStore.SearchDashboardSnapshots(query) +func (s *Service) SearchDashboardSnapshots(ctx context.Context, query *models.GetDashboardSnapshotsQuery) error { + return s.SQLStore.SearchDashboardSnapshots(ctx, query) } func (s *Service) DeleteExpiredSnapshots(ctx context.Context, cmd *models.DeleteExpiredSnapshotsCommand) error { diff --git a/pkg/services/datasources/service/datasource_service.go b/pkg/services/datasources/service/datasource_service.go index 914f5ed0b26..dbc22e6e261 100644 --- a/pkg/services/datasources/service/datasource_service.go +++ b/pkg/services/datasources/service/datasource_service.go @@ -33,6 +33,7 @@ type Service struct { cfg *setting.Cfg features featuremgmt.FeatureToggles permissionsService accesscontrol.PermissionsService + ac accesscontrol.AccessControl ptc proxyTransportCache dsDecryptionCache secureJSONDecryptionCache @@ -74,6 +75,7 @@ func ProvideService( cfg: cfg, features: features, permissionsService: permissionsServices.GetDataSourceService(), + ac: ac, } ac.RegisterAttributeScopeResolver(NewNameScopeResolver(store)) @@ -162,7 +164,7 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCo return err } - if s.features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !s.ac.IsDisabled() { // This belongs in Data source permissions, and we probably want // to do this with a hook in the store and rollback on fail. // We can't use events, because there's no way to communicate diff --git a/pkg/services/datasources/service/datasource_service_test.go b/pkg/services/datasources/service/datasource_service_test.go index 511b0f8c9b2..2df5cb86f50 100644 --- a/pkg/services/datasources/service/datasource_service_test.go +++ b/pkg/services/datasources/service/datasource_service_test.go @@ -38,7 +38,7 @@ func TestService(t *testing.T) { }) secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) - s := ProvideService(sqlStore, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock()) + s := ProvideService(sqlStore, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New().WithDisabled(), acmock.NewPermissionsServicesMock()) var ds *models.DataSource diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 60b46d46b6a..a96c4249061 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -24,7 +24,7 @@ var ( State: FeatureStateBeta, }, { - Name: "service-accounts", + Name: "serviceAccounts", Description: "support service accounts", State: FeatureStateBeta, }, @@ -39,14 +39,10 @@ var ( State: FeatureStateAlpha, }, { - Name: "dashboardPreviewsScheduler", - Description: "Schedule automatic updates to dashboard previews", - State: FeatureStateAlpha, - }, - { - Name: "dashboardPreviewsAdmin", - Description: "Manage the dashboard previews crawler process from the UI", - State: FeatureStateAlpha, + Name: "dashboardPreviewsAdmin", + Description: "Manage the dashboard previews crawler process from the UI", + State: FeatureStateAlpha, + RequiresDevMode: true, }, { Name: "live-config", @@ -213,5 +209,16 @@ var ( State: FeatureStateBeta, FrontendOnly: true, }, + { + Name: "persistNotifications", + Description: "PoC Notifications page", + State: FeatureStateAlpha, + FrontendOnly: true, + }, + { + Name: "commandPalette", + Description: "Enable command palette", + State: FeatureStateAlpha, + }, } ) diff --git a/pkg/services/featuremgmt/strcase/camel.go b/pkg/services/featuremgmt/strcase/camel.go new file mode 100644 index 00000000000..4e355df5975 --- /dev/null +++ b/pkg/services/featuremgmt/strcase/camel.go @@ -0,0 +1,92 @@ +// https://github.com/iancoleman/strcase/blob/master/camel.go +// https://github.com/iancoleman/strcase/blob/master/acronyms.go + +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Ian Coleman + * Copyright (c) 2018 Ma_124, + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, Subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or Substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package strcase + +import ( + "strings" +) + +var uppercaseAcronym = map[string]string{ + "ID": "id", +} + +// ConfigureAcronym allows you to add additional words which will be considered acronyms +func ConfigureAcronym(key, val string) { + uppercaseAcronym[key] = val +} + +// Converts a string to CamelCase +func toCamelInitCase(s string, initCase bool) string { + s = strings.TrimSpace(s) + if s == "" { + return s + } + if a, ok := uppercaseAcronym[s]; ok { + s = a + } + + n := strings.Builder{} + n.Grow(len(s)) + capNext := initCase + for i, v := range []byte(s) { + vIsCap := v >= 'A' && v <= 'Z' + vIsLow := v >= 'a' && v <= 'z' + if capNext { + if vIsLow { + v += 'A' + v -= 'a' + } + } else if i == 0 { + if vIsCap { + v += 'a' + v -= 'A' + } + } + if vIsCap || vIsLow { + n.WriteByte(v) + capNext = false + } else if vIsNum := v >= '0' && v <= '9'; vIsNum { + n.WriteByte(v) + capNext = true + } else { + capNext = v == '_' || v == ' ' || v == '-' || v == '.' + } + } + return n.String() +} + +// ToCamel converts a string to CamelCase +func ToCamel(s string) string { + return toCamelInitCase(s, true) +} + +// ToLowerCamel converts a string to lowerCamelCase +func ToLowerCamel(s string) string { + return toCamelInitCase(s, false) +} diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 7548ed76643..31978faca12 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -21,7 +21,7 @@ const ( // FlagServiceAccounts // support service accounts - FlagServiceAccounts = "service-accounts" + FlagServiceAccounts = "serviceAccounts" // FlagDatabaseMetrics // Add prometheus metrics for database tables @@ -31,10 +31,6 @@ const ( // Create and show thumbnails for dashboard search results FlagDashboardPreviews = "dashboardPreviews" - // FlagDashboardPreviewsScheduler - // Schedule automatic updates to dashboard previews - FlagDashboardPreviewsScheduler = "dashboardPreviewsScheduler" - // FlagDashboardPreviewsAdmin // Manage the dashboard previews crawler process from the UI FlagDashboardPreviewsAdmin = "dashboardPreviewsAdmin" @@ -158,4 +154,12 @@ const ( // FlagExplore2Dashboard // Experimental Explore to Dashboard workflow FlagExplore2Dashboard = "explore2Dashboard" + + // FlagPersistNotifications + // PoC Notifications page + FlagPersistNotifications = "persistNotifications" + + // FlagCommandPalette + // Enable command palette + FlagCommandPalette = "commandPalette" ) diff --git a/pkg/services/featuremgmt/toggles_gen_test.go b/pkg/services/featuremgmt/toggles_gen_test.go index 27c3f05a753..8bc082537e7 100644 --- a/pkg/services/featuremgmt/toggles_gen_test.go +++ b/pkg/services/featuremgmt/toggles_gen_test.go @@ -12,20 +12,52 @@ import ( "unicode" "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana/pkg/services/featuremgmt/strcase" + "github.com/stretchr/testify/require" ) func TestFeatureToggleFiles(t *testing.T) { - // Typescript files - verifyAndGenerateFile(t, - "../../../packages/grafana-data/src/types/featureToggles.gen.ts", - generateTypeScript(), - ) + legacyNames := map[string]bool{ + "httpclientprovider_azure_auth": true, + "service-accounts": true, + "database_metrics": true, + "live-config": true, + "live-pipeline": true, + "live-service-web-worker": true, + "accesscontrol-builtins": true, + "prometheus_azure_auth": true, + "disable_http_request_histogram": true, + } - // Golang files - verifyAndGenerateFile(t, - "toggles_gen.go", - generateRegistry(t), - ) + t.Run("verify files", func(t *testing.T) { + // Typescript files + verifyAndGenerateFile(t, + "../../../packages/grafana-data/src/types/featureToggles.gen.ts", + generateTypeScript(), + ) + + // Golang files + verifyAndGenerateFile(t, + "toggles_gen.go", + generateRegistry(t), + ) + }) + + t.Run("check feature naming convention", func(t *testing.T) { + invalidNames := make([]string, 0) + for _, f := range standardFeatureFlags { + if legacyNames[f.Name] { + continue + } + + if f.Name != strcase.ToLowerCamel(f.Name) { + invalidNames = append(invalidNames, f.Name) + } + } + + require.Empty(t, invalidNames, "%s feature names should be camel cased", invalidNames) + // acronyms can be configured as needed via `ConfigureAcronym` function from `./strcase/camel.go` + }) } func verifyAndGenerateFile(t *testing.T, fpath string, gen string) { diff --git a/pkg/services/featuremgmt/usage_stats_test.go b/pkg/services/featuremgmt/usage_stats_test.go index 1bc5420e4ff..ca044aaece2 100644 --- a/pkg/services/featuremgmt/usage_stats_test.go +++ b/pkg/services/featuremgmt/usage_stats_test.go @@ -11,7 +11,7 @@ func TestFeatureUsageStats(t *testing.T) { featureManagerWithAllFeatures := WithFeatures( "trimDefaults", "httpclientprovider_azure_auth", - "service-accounts", + "serviceAccounts", "database_metrics", "dashboardPreviews", "live-config", diff --git a/pkg/services/guardian/provider.go b/pkg/services/guardian/provider.go index 5acccdf7774..3318fb45abb 100644 --- a/pkg/services/guardian/provider.go +++ b/pkg/services/guardian/provider.go @@ -12,7 +12,7 @@ import ( type Provider struct{} func ProvideService(store *sqlstore.SQLStore, ac accesscontrol.AccessControl, permissionsServices accesscontrol.PermissionsServices, features featuremgmt.FeatureToggles) *Provider { - if features.IsEnabled(featuremgmt.FlagAccesscontrol) { + if !ac.IsDisabled() { // TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935 InitAcessControlGuardian(store, ac, permissionsServices) } else { diff --git a/pkg/services/ldap/helpers.go b/pkg/services/ldap/helpers.go index 12438f23f6c..851725f2d19 100644 --- a/pkg/services/ldap/helpers.go +++ b/pkg/services/ldap/helpers.go @@ -6,7 +6,7 @@ import ( "gopkg.in/ldap.v3" ) -func isMemberOf(memberOf []string, group string) bool { +func IsMemberOf(memberOf []string, group string) bool { if group == "*" { return true } diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index 1ad3fa15905..02f9e2b2f13 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -422,7 +422,7 @@ func (server *Server) buildGrafanaUser(user *ldap.Entry) (*models.ExternalUserIn continue } - if isMemberOf(memberOf, group.GroupDN) { + if IsMemberOf(memberOf, group.GroupDN) { extUser.OrgRoles[group.OrgId] = group.OrgRole if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin { extUser.IsGrafanaAdmin = group.IsGrafanaAdmin diff --git a/pkg/services/ldap/ldap_helpers_test.go b/pkg/services/ldap/ldap_helpers_test.go index e276917c973..887a2a0a5df 100644 --- a/pkg/services/ldap/ldap_helpers_test.go +++ b/pkg/services/ldap/ldap_helpers_test.go @@ -21,7 +21,7 @@ func TestIsMemberOf(t *testing.T) { for _, tc := range tests { t.Run(fmt.Sprintf("isMemberOf(%v, \"%s\") = %v", tc.memberOf, tc.group, tc.expected), func(t *testing.T) { - assert.Equal(t, tc.expected, isMemberOf(tc.memberOf, tc.group)) + assert.Equal(t, tc.expected, IsMemberOf(tc.memberOf, tc.group)) }) } } diff --git a/pkg/services/librarypanels/librarypanels.go b/pkg/services/librarypanels/librarypanels.go index c23268a03a8..95ec44e4b1d 100644 --- a/pkg/services/librarypanels/librarypanels.go +++ b/pkg/services/librarypanels/librarypanels.go @@ -81,14 +81,12 @@ func loadLibraryPanelsRecursively(elements map[string]libraryelements.LibraryEle elementInDB, ok := elements[UID] if !ok { - name := libraryPanel.Get("name").MustString() elem := parent.Get("panels").GetIndex(i) elem.Set("gridPos", panelAsJSON.Get("gridPos").MustMap()) elem.Set("id", panelAsJSON.Get("id").MustInt64()) - elem.Set("type", fmt.Sprintf("Name: \"%s\", UID: \"%s\"", name, UID)) + elem.Set("type", fmt.Sprintf("Library panel with UID: \"%s\"", UID)) elem.Set("libraryPanel", map[string]interface{}{ - "uid": UID, - "name": name, + "uid": UID, }) continue } @@ -174,10 +172,6 @@ func cleanLibraryPanelsRecursively(parent *simplejson.Json) error { if len(UID) == 0 { return errLibraryPanelHeaderUIDMissing } - name := libraryPanel.Get("name").MustString() - if len(name) == 0 { - return errLibraryPanelHeaderNameMissing - } // keep only the necessary JSON properties, the rest of the properties should be safely stored in library_panels table gridPos := panelAsJSON.Get("gridPos").MustMap() @@ -186,8 +180,7 @@ func cleanLibraryPanelsRecursively(parent *simplejson.Json) error { "id": ID, "gridPos": gridPos, "libraryPanel": map[string]interface{}{ - "uid": UID, - "name": name, + "uid": UID, }, }) } diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 26f28a798c2..cfd3a9896e9 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -186,8 +186,7 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, "title": "Inside row", "type": "text", @@ -204,8 +203,7 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": outsidePanel.UID, - "name": outsidePanel.Name, + "uid": outsidePanel.UID, }, "title": "Outside row", "type": "text", @@ -401,8 +399,7 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) { "y": 0, }, "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, }, }, @@ -438,10 +435,9 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) { "y": 0, }, "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, - "type": fmt.Sprintf("Name: \"%s\", UID: \"%s\"", sc.initialResult.Result.Name, sc.initialResult.Result.UID), + "type": fmt.Sprintf("Library panel with UID: \"%s\"", sc.initialResult.Result.UID), }, }, } @@ -476,8 +472,7 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, "title": "Text - Library Panel", "type": "text", @@ -515,8 +510,7 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { "y": 0, }, "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, }, }, @@ -586,8 +580,7 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, "title": "Inside row", "type": "text", @@ -604,8 +597,7 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": outsidePanel.UID, - "name": outsidePanel.Name, + "uid": outsidePanel.UID, }, "title": "Outside row", "type": "text", @@ -663,8 +655,7 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { "y": 13, }, "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, }, }, @@ -678,8 +669,7 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { "y": 19, }, "libraryPanel": map[string]interface{}{ - "uid": outsidePanel.UID, - "name": outsidePanel.Name, + "uid": outsidePanel.UID, }, }, }, @@ -729,46 +719,6 @@ func TestCleanLibraryPanelsForDashboard(t *testing.T) { err := sc.service.CleanLibraryPanelsForDashboard(dashInDB) require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error()) }) - - scenarioWithLibraryPanel(t, "When an admin tries to store a dashboard with a library panel without name, it should fail", - func(t *testing.T, sc scenarioContext) { - dashJSON := map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": int64(1), - "gridPos": map[string]interface{}{ - "h": 6, - "w": 6, - "x": 0, - "y": 0, - }, - }, - map[string]interface{}{ - "id": int64(2), - "gridPos": map[string]interface{}{ - "h": 6, - "w": 6, - "x": 6, - "y": 0, - }, - "datasource": "${DS_GDEV-TESTDATA}", - "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - }, - "title": "Text - Library Panel", - "type": "text", - }, - }, - } - dash := models.Dashboard{ - Title: "Testing CleanLibraryPanelsForDashboard", - Data: simplejson.NewFromAny(dashJSON), - } - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) - - err := sc.service.CleanLibraryPanelsForDashboard(dashInDB) - require.EqualError(t, err, errLibraryPanelHeaderNameMissing.Error()) - }) } func TestConnectLibraryPanelsForDashboard(t *testing.T) { @@ -795,8 +745,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, "title": "Text - Library Panel", "type": "text", @@ -877,8 +826,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, "title": "Inside row", "type": "text", @@ -895,8 +843,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": outsidePanel.UID, - "name": outsidePanel.Name, + "uid": outsidePanel.UID, }, "title": "Outside row", "type": "text", @@ -997,8 +944,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": unused.UID, - "name": unused.Name, + "uid": unused.UID, }, "title": "Unused Libray Panel", "description": "Unused description", @@ -1034,8 +980,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { }, "datasource": "${DS_GDEV-TESTDATA}", "libraryPanel": map[string]interface{}{ - "uid": sc.initialResult.Result.UID, - "name": sc.initialResult.Result.Name, + "uid": sc.initialResult.Result.UID, }, "title": "Text - Library Panel", "type": "text", diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md index 15d9aadcf60..6738445a117 100644 --- a/pkg/services/ngalert/CHANGELOG.md +++ b/pkg/services/ngalert/CHANGELOG.md @@ -49,3 +49,6 @@ Scopes must have an order to ensure consistency and ease of search, this helps u - [BUGFIX] (Legacy) Templates: Parse notification templates using all the matches of the alert rule when going from `Alerting` to `OK` in legacy alerting #47355 - [BUGFIX] Scheduler: Fix state manager to support OK option of `AlertRule.ExecErrState` #47670 - [ENHANCEMENT] Templates: Enable the use of classic condition values in templates #46971 +- [ENHANCEMENT] Scheduler: ticker expose new metrics `grafana_alerting_ticker_last_consumed_tick_timestamp_seconds`, `grafana_alerting_ticker_next_tick_timestamp_seconds`, `grafana_alerting_ticker_interval_seconds` #47828 +- [CHANGE] Notification URL points to alert view page instead of alert edit page. #47752 +- [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857 diff --git a/pkg/services/ngalert/accesscontrol.go b/pkg/services/ngalert/accesscontrol.go index 2689ef03dc1..6c77aff47a8 100644 --- a/pkg/services/ngalert/accesscontrol.go +++ b/pkg/services/ngalert/accesscontrol.go @@ -165,7 +165,7 @@ var ( Version: 2, Permissions: accesscontrol.ConcatPermissions(rulesEditorRole.Role.Permissions, instancesEditorRole.Role.Permissions, notificationsEditorRole.Role.Permissions), }, - Grants: []string{string(models.ROLE_EDITOR)}, + Grants: []string{string(models.ROLE_EDITOR), string(models.ROLE_ADMIN)}, } ) diff --git a/pkg/services/ngalert/api/api_alertmanager_test.go b/pkg/services/ngalert/api/api_alertmanager_test.go index eec2b513e00..45c886cd1e4 100644 --- a/pkg/services/ngalert/api/api_alertmanager_test.go +++ b/pkg/services/ngalert/api/api_alertmanager_test.go @@ -2,6 +2,7 @@ package api import ( "context" + "encoding/json" "math/rand" "net/http" "testing" @@ -12,14 +13,17 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/featuremgmt" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/metrics" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/secrets/fakes" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/setting" @@ -208,6 +212,31 @@ func TestAlertmanagerConfig(t *testing.T) { require.Equal(t, 202, response.Status()) }) + + t.Run("when objects are not provisioned", func(t *testing.T) { + t.Run("route from GET config has no provenance", func(t *testing.T) { + sut := createSut(t, nil) + rc := createRequestCtxInOrg(1) + + response := sut.RouteGetAlertingConfig(rc) + + body := asGettableUserConfig(t, response) + require.Equal(t, ngmodels.ProvenanceNone, body.AlertmanagerConfig.Route.Provenance) + }) + }) + + t.Run("when objects are provisioned", func(t *testing.T) { + t.Run("route from GET config has expected provenance", func(t *testing.T) { + sut := createSut(t, nil) + rc := createRequestCtxInOrg(1) + setRouteProvenance(t, 1, sut.mam.ProvStore) + + response := sut.RouteGetAlertingConfig(rc) + + body := asGettableUserConfig(t, response) + require.Equal(t, ngmodels.ProvenanceAPI, body.AlertmanagerConfig.Route.Provenance) + }) + }) } func TestRouteCreateSilence(t *testing.T) { @@ -219,7 +248,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus int }{ { - name: "new silence, fine-grained access control is enabled, not authorized", + name: "new silence, role-based access control is enabled, not authorized", silence: silenceGen(withEmptyID), accessControl: func() accesscontrol.AccessControl { return acMock.New() @@ -227,7 +256,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusUnauthorized, }, { - name: "new silence, fine-grained access control is enabled, authorized", + name: "new silence, role-based access control is enabled, authorized", silence: silenceGen(withEmptyID), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithPermissions([]*accesscontrol.Permission{ @@ -237,7 +266,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusAccepted, }, { - name: "new silence, fine-grained access control is disabled, Viewer", + name: "new silence, role-based access control is disabled, Viewer", silence: silenceGen(withEmptyID), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithDisabled() @@ -246,7 +275,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusUnauthorized, }, { - name: "new silence, fine-grained access control is disabled, Editor", + name: "new silence, role-based access control is disabled, Editor", silence: silenceGen(withEmptyID), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithDisabled() @@ -255,7 +284,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusAccepted, }, { - name: "new silence, fine-grained access control is disabled, Admin", + name: "new silence, role-based access control is disabled, Admin", silence: silenceGen(withEmptyID), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithDisabled() @@ -264,7 +293,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusAccepted, }, { - name: "update silence, fine-grained access control is enabled, not authorized", + name: "update silence, role-based access control is enabled, not authorized", silence: silenceGen(), accessControl: func() accesscontrol.AccessControl { return acMock.New() @@ -272,7 +301,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusUnauthorized, }, { - name: "update silence, fine-grained access control is enabled, authorized", + name: "update silence, role-based access control is enabled, authorized", silence: silenceGen(), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithPermissions([]*accesscontrol.Permission{ @@ -282,7 +311,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusAccepted, }, { - name: "update silence, fine-grained access control is disabled, Viewer", + name: "update silence, role-based access control is disabled, Viewer", silence: silenceGen(), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithDisabled() @@ -291,7 +320,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusUnauthorized, }, { - name: "update silence, fine-grained access control is disabled, Editor", + name: "update silence, role-based access control is disabled, Editor", silence: silenceGen(), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithDisabled() @@ -300,7 +329,7 @@ func TestRouteCreateSilence(t *testing.T) { expectedStatus: http.StatusAccepted, }, { - name: "update silence, fine-grained access control is disabled, Admin", + name: "update silence, role-based access control is disabled, Admin", silence: silenceGen(), accessControl: func() accesscontrol.AccessControl { return acMock.New().WithDisabled() @@ -346,19 +375,16 @@ func createSut(t *testing.T, accessControl accesscontrol.AccessControl) Alertman t.Helper() mam := createMultiOrgAlertmanager(t) - configs := map[int64]*ngmodels.AlertConfiguration{ - 1: {AlertmanagerConfiguration: validConfig, OrgID: 1}, - 2: {AlertmanagerConfiguration: validConfig, OrgID: 2}, - 3: {AlertmanagerConfiguration: brokenConfig, OrgID: 3}, - } - configStore := notifier.NewFakeConfigStore(t, configs) - secrets := fakes.NewFakeSecretsService() if accessControl == nil { accessControl = acMock.New().WithDisabled() } log := log.NewNopLogger() - crypto := notifier.NewCrypto(secrets, &configStore, log) - return AlertmanagerSrv{mam: mam, crypto: crypto, ac: accessControl, log: log} + return AlertmanagerSrv{ + mam: mam, + crypto: mam.Crypto, + ac: accessControl, + log: log, + } } func createAmConfigRequest(t *testing.T) apimodels.PostableUserConfig { @@ -381,6 +407,7 @@ func createMultiOrgAlertmanager(t *testing.T) *notifier.MultiOrgAlertmanager { } configStore := notifier.NewFakeConfigStore(t, configs) orgStore := notifier.NewFakeOrgStore(t, []int64{1, 2, 3}) + provStore := provisioning.NewFakeProvisioningStore() tmpDir := t.TempDir() kvStore := notifier.NewFakeKVStore(t) secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) @@ -394,9 +421,12 @@ func createMultiOrgAlertmanager(t *testing.T) *notifier.MultiOrgAlertmanager { DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(), DisabledOrgs: map[int64]struct{}{5: {}}, }, // do not poll in tests. + IsFeatureToggleEnabled: func(key string) bool { + return key == featuremgmt.FlagAlertProvisioning + }, } - mam, err := notifier.NewMultiOrgAlertmanager(cfg, &configStore, &orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) + mam, err := notifier.NewMultiOrgAlertmanager(cfg, &configStore, &orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) err = mam.LoadAndSyncAlertmanagersForOrgs(context.Background()) require.NoError(t, err) @@ -460,3 +490,30 @@ func silenceGen(mutatorFuncs ...func(*apimodels.PostableSilence)) func() apimode func withEmptyID(silence *apimodels.PostableSilence) { silence.ID = "" } + +func createRequestCtxInOrg(org int64) *models.ReqContext { + return &models.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + SignedInUser: &models.SignedInUser{ + OrgId: org, + }, + } +} + +// setRouteProvenance marks an org's routing tree as provisioned. +func setRouteProvenance(t *testing.T, org int64, ps provisioning.ProvisioningStore) { + t.Helper() + adp := provisioning.ProvenanceOrgAdapter{Inner: &apimodels.Route{}, OrgID: org} + err := ps.SetProvenance(context.Background(), adp, ngmodels.ProvenanceAPI) + require.NoError(t, err) +} + +func asGettableUserConfig(t *testing.T, r response.Response) *apimodels.GettableUserConfig { + t.Helper() + body := &apimodels.GettableUserConfig{} + err := json.Unmarshal(r.Body(), body) + require.NoError(t, err) + return body +} diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index c7ee7b93b97..15759737c14 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -147,7 +147,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res DashboardUID: dashboardUID, PanelID: panelID, } - if err := srv.store.GetOrgAlertRules(c.Req.Context(), &alertRuleQuery); err != nil { + if err := srv.store.ListAlertRules(c.Req.Context(), &alertRuleQuery); err != nil { ruleResponse.DiscoveryBase.Status = "error" ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rules: %s", err.Error()) ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index 2b6e632c58e..c8113be9abb 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/models" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -30,7 +29,7 @@ type ContactPointService interface { } type NotificationPolicyService interface { - GetPolicyTree(ctx context.Context, orgID int64) (provisioning.EmbeddedRoutingTree, error) + GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p alerting_models.Provenance) error } diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 5cac3a26a1b..e618f4091d2 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/models" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" domain "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/require" @@ -120,14 +119,13 @@ func newFakeNotificationPolicyService() *fakeNotificationPolicyService { } } -func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (provisioning.EmbeddedRoutingTree, error) { +func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) { if orgID != 1 { - return provisioning.EmbeddedRoutingTree{}, store.ErrNoAlertmanagerConfiguration + return apimodels.Route{}, store.ErrNoAlertmanagerConfiguration } - return provisioning.EmbeddedRoutingTree{ - Route: f.tree, - Provenance: f.prov, - }, nil + result := f.tree + result.Provenance = f.prov + return result, nil } func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error { @@ -141,8 +139,8 @@ func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, or type fakeFailingNotificationPolicyService struct{} -func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (provisioning.EmbeddedRoutingTree, error) { - return provisioning.EmbeddedRoutingTree{}, fmt.Errorf("something went wrong") +func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) { + return apimodels.Route{}, fmt.Errorf("something went wrong") } func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error { diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 963597e7e9c..f8f9a43a10f 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -55,9 +55,9 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *models.ReqContext) response.Respons "namespace", namespace.Title, } - var ruleGroup *string + var ruleGroup string if group, ok := web.Params(c.Req)[":Groupname"]; ok { - ruleGroup = &group + ruleGroup = group loggerCtx = append(loggerCtx, "group", group) } logger := srv.log.New(loggerCtx...) @@ -68,12 +68,12 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *models.ReqContext) response.Respons var canDelete, cannotDelete []string err = srv.xactManager.InTransaction(c.Req.Context(), func(ctx context.Context) error { - q := ngmodels.GetAlertRulesQuery{ - OrgID: c.SignedInUser.OrgId, - NamespaceUID: namespace.Uid, - RuleGroup: ruleGroup, + q := ngmodels.ListAlertRulesQuery{ + OrgID: c.SignedInUser.OrgId, + NamespaceUIDs: []string{namespace.Uid}, + RuleGroup: ruleGroup, } - if err = srv.store.GetAlertRules(ctx, &q); err != nil { + if err = srv.store.ListAlertRules(ctx, &q); err != nil { return err } @@ -128,11 +128,11 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response. return toNamespaceErrorResponse(err) } - q := ngmodels.GetAlertRulesQuery{ - OrgID: c.SignedInUser.OrgId, - NamespaceUID: namespace.Uid, + q := ngmodels.ListAlertRulesQuery{ + OrgID: c.SignedInUser.OrgId, + NamespaceUIDs: []string{namespace.Uid}, } - if err := srv.store.GetAlertRules(c.Req.Context(), &q); err != nil { + if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to update rule group") } @@ -178,12 +178,12 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp } ruleGroup := web.Params(c.Req)[":Groupname"] - q := ngmodels.GetAlertRulesQuery{ - OrgID: c.SignedInUser.OrgId, - NamespaceUID: namespace.Uid, - RuleGroup: &ruleGroup, + q := ngmodels.ListAlertRulesQuery{ + OrgID: c.SignedInUser.OrgId, + NamespaceUIDs: []string{namespace.Uid}, + RuleGroup: ruleGroup, } - if err := srv.store.GetAlertRules(c.Req.Context(), &q); err != nil { + if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get group alert rules") } @@ -245,7 +245,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response PanelID: panelID, } - if err := srv.store.GetOrgAlertRules(c.Req.Context(), &q); err != nil { + if err := srv.store.ListAlertRules(c.Req.Context(), &q); err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules") } @@ -493,12 +493,12 @@ func (c *changes) isEmpty() bool { // calculateChanges calculates the difference between rules in the group in the database and the submitted rules. If a submitted rule has UID it tries to find it in the database (in other groups). // returns a list of rules that need to be added, updated and deleted. Deleted considered rules in the database that belong to the group but do not exist in the list of submitted rules. func calculateChanges(ctx context.Context, ruleStore store.RuleStore, orgId int64, namespace *models.Folder, ruleGroupName string, submittedRules []*ngmodels.AlertRule) (*changes, error) { - q := &ngmodels.GetAlertRulesQuery{ - OrgID: orgId, - NamespaceUID: namespace.Uid, - RuleGroup: &ruleGroupName, + q := &ngmodels.ListAlertRulesQuery{ + OrgID: orgId, + NamespaceUIDs: []string{namespace.Uid}, + RuleGroup: ruleGroupName, } - if err := ruleStore.GetAlertRules(ctx, q); err != nil { + if err := ruleStore.ListAlertRules(ctx, q); err != nil { return nil, fmt.Errorf("failed to query database for rules in the group %s: %w", ruleGroupName, err) } existingGroupRules := q.Result diff --git a/pkg/services/ngalert/api/api_ruler_test.go b/pkg/services/ngalert/api/api_ruler_test.go index 9a40a93dfbd..1d10b29c80d 100644 --- a/pkg/services/ngalert/api/api_ruler_test.go +++ b/pkg/services/ngalert/api/api_ruler_test.go @@ -232,7 +232,7 @@ func TestCalculateChanges(t *testing.T) { expectedErr := errors.New("TEST ERROR") fakeStore.Hook = func(cmd interface{}) error { switch cmd.(type) { - case models.GetAlertRulesQuery: + case models.ListAlertRulesQuery: return expectedErr } return nil diff --git a/pkg/services/ngalert/api/api_testing.go b/pkg/services/ngalert/api/api_testing.go index 3df26ad261c..1449aee50f5 100644 --- a/pkg/services/ngalert/api/api_testing.go +++ b/pkg/services/ngalert/api/api_testing.go @@ -70,13 +70,13 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body a } func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response { - recipient := web.Params(c.Req)[":Recipient"] + datasourceID := web.Params(c.Req)[":DatasourceID"] if body.Type() != apimodels.LoTexRulerBackend { return ErrResp(http.StatusBadRequest, errors.New("unexpected payload"), "") } var path string - if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil { + if datasourceID, err := strconv.ParseInt(datasourceID, 10, 64); err == nil { ds, err := srv.DatasourceCache.GetDatasource(context.Background(), datasourceID, c.SignedInUser, c.SkipCache) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get datasource") @@ -88,7 +88,7 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodel case "prometheus": path = "api/v1/query" default: - return ErrResp(http.StatusBadRequest, fmt.Errorf("unexpected recipient type %s", ds.Type), "") + return ErrResp(http.StatusBadRequest, fmt.Errorf("unexpected datasource type %s", ds.Type), "") } } diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index 6394ec50ac3..b910571e26a 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -75,27 +75,27 @@ func (api *API) authorize(method, path string) web.Handler { eval = ac.EvalPermission(ac.ActionAlertingRuleRead) // Lotex Paths - case http.MethodDelete + "/api/ruler/{Recipient}/api/v1/rules/{Namespace}": - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodDelete + "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}": - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/ruler/{Recipient}/api/v1/rules/{Namespace}": - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}": - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/ruler/{Recipient}/api/v1/rules": - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodPost + "/api/ruler/{Recipient}/api/v1/rules/{Namespace}": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + case http.MethodDelete + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}": + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodDelete + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}": + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}": + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}": + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/ruler/{DatasourceID}/api/v1/rules": + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodPost + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Lotex Prometheus-compatible Paths - case http.MethodGet + "/api/prometheus/{Recipient}/api/v1/rules": - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + case http.MethodGet + "/api/prometheus/{DatasourceID}/api/v1/rules": + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Lotex Rules testing - case http.MethodPost + "/api/v1/rule/test/{Recipient}": + case http.MethodPost + "/api/v1/rule/test/{DatasourceID}": fallback = middleware.ReqSignedIn - eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Alert Instances and Silences @@ -123,26 +123,26 @@ func (api *API) authorize(method, path string) web.Handler { eval = ac.EvalPermission(ac.ActionAlertingInstanceRead) // Silences. External AM. - case http.MethodDelete + "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodPost + "/api/alertmanager/{Recipient}/api/v2/silences": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/alertmanager/{Recipient}/api/v2/silences": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + case http.MethodDelete + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodPost + "/api/alertmanager/{DatasourceID}/api/v2/silences": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/silences": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Alert instances. External AM. - case http.MethodGet + "/api/alertmanager/{Recipient}/api/v2/alerts/groups": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/alertmanager/{Recipient}/api/v2/alerts": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodPost + "/api/alertmanager/{Recipient}/api/v2/alerts": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/alerts/groups": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/alerts": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodPost + "/api/alertmanager/{DatasourceID}/api/v2/alerts": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Prometheus-compatible Paths - case http.MethodGet + "/api/prometheus/{Recipient}/api/v1/alerts": - eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + case http.MethodGet + "/api/prometheus/{DatasourceID}/api/v1/alerts": + eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Notification Policies, Contact Points and Templates @@ -162,16 +162,16 @@ func (api *API) authorize(method, path string) web.Handler { eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead) // External Alertmanager Paths - case http.MethodDelete + "/api/alertmanager/{Recipient}/config/api/v1/alerts": - eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/alertmanager/{Recipient}/api/v2/status": - eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodGet + "/api/alertmanager/{Recipient}/config/api/v1/alerts": - eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodPost + "/api/alertmanager/{Recipient}/config/api/v1/alerts": - eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) - case http.MethodPost + "/api/alertmanager/{Recipient}/config/api/v1/receivers/test": - eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient"))) + case http.MethodDelete + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts": + eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/status": + eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodGet + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts": + eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodPost + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts": + eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) + case http.MethodPost + "/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test": + eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID"))) // Raw Alertmanager Config Paths case http.MethodDelete + "/api/v1/ngalert/admin_config", diff --git a/pkg/services/ngalert/api/generated_base_api_alertmanager.go b/pkg/services/ngalert/api/generated_base_api_alertmanager.go index 69d9a06a570..e6539e2183e 100644 --- a/pkg/services/ngalert/api/generated_base_api_alertmanager.go +++ b/pkg/services/ngalert/api/generated_base_api_alertmanager.go @@ -187,21 +187,21 @@ func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiForkingServi ), ) group.Post( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), - api.authorize(http.MethodPost, "/api/alertmanager/{Recipient}/api/v2/silences"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/silences"), + api.authorize(http.MethodPost, "/api/alertmanager/{DatasourceID}/api/v2/silences"), metrics.Instrument( http.MethodPost, - "/api/alertmanager/{Recipient}/api/v2/silences", + "/api/alertmanager/{DatasourceID}/api/v2/silences", srv.RouteCreateSilence, m, ), ) group.Delete( - toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), - api.authorize(http.MethodDelete, "/api/alertmanager/{Recipient}/config/api/v1/alerts"), + toMacaronPath("/api/alertmanager/{DatasourceID}/config/api/v1/alerts"), + api.authorize(http.MethodDelete, "/api/alertmanager/{DatasourceID}/config/api/v1/alerts"), metrics.Instrument( http.MethodDelete, - "/api/alertmanager/{Recipient}/config/api/v1/alerts", + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts", srv.RouteDeleteAlertingConfig, m, ), @@ -227,51 +227,51 @@ func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiForkingServi ), ) group.Delete( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), - api.authorize(http.MethodDelete, "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}"), + api.authorize(http.MethodDelete, "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}"), metrics.Instrument( http.MethodDelete, - "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}", + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}", srv.RouteDeleteSilence, m, ), ) group.Get( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts/groups"), - api.authorize(http.MethodGet, "/api/alertmanager/{Recipient}/api/v2/alerts/groups"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/alerts/groups"), + api.authorize(http.MethodGet, "/api/alertmanager/{DatasourceID}/api/v2/alerts/groups"), metrics.Instrument( http.MethodGet, - "/api/alertmanager/{Recipient}/api/v2/alerts/groups", + "/api/alertmanager/{DatasourceID}/api/v2/alerts/groups", srv.RouteGetAMAlertGroups, m, ), ) group.Get( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), - api.authorize(http.MethodGet, "/api/alertmanager/{Recipient}/api/v2/alerts"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/alerts"), + api.authorize(http.MethodGet, "/api/alertmanager/{DatasourceID}/api/v2/alerts"), metrics.Instrument( http.MethodGet, - "/api/alertmanager/{Recipient}/api/v2/alerts", + "/api/alertmanager/{DatasourceID}/api/v2/alerts", srv.RouteGetAMAlerts, m, ), ) group.Get( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/status"), - api.authorize(http.MethodGet, "/api/alertmanager/{Recipient}/api/v2/status"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/status"), + api.authorize(http.MethodGet, "/api/alertmanager/{DatasourceID}/api/v2/status"), metrics.Instrument( http.MethodGet, - "/api/alertmanager/{Recipient}/api/v2/status", + "/api/alertmanager/{DatasourceID}/api/v2/status", srv.RouteGetAMStatus, m, ), ) group.Get( - toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), - api.authorize(http.MethodGet, "/api/alertmanager/{Recipient}/config/api/v1/alerts"), + toMacaronPath("/api/alertmanager/{DatasourceID}/config/api/v1/alerts"), + api.authorize(http.MethodGet, "/api/alertmanager/{DatasourceID}/config/api/v1/alerts"), metrics.Instrument( http.MethodGet, - "/api/alertmanager/{Recipient}/config/api/v1/alerts", + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts", srv.RouteGetAlertingConfig, m, ), @@ -337,41 +337,41 @@ func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiForkingServi ), ) group.Get( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), - api.authorize(http.MethodGet, "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}"), + api.authorize(http.MethodGet, "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}"), metrics.Instrument( http.MethodGet, - "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}", + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}", srv.RouteGetSilence, m, ), ) group.Get( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), - api.authorize(http.MethodGet, "/api/alertmanager/{Recipient}/api/v2/silences"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/silences"), + api.authorize(http.MethodGet, "/api/alertmanager/{DatasourceID}/api/v2/silences"), metrics.Instrument( http.MethodGet, - "/api/alertmanager/{Recipient}/api/v2/silences", + "/api/alertmanager/{DatasourceID}/api/v2/silences", srv.RouteGetSilences, m, ), ) group.Post( - toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), - api.authorize(http.MethodPost, "/api/alertmanager/{Recipient}/api/v2/alerts"), + toMacaronPath("/api/alertmanager/{DatasourceID}/api/v2/alerts"), + api.authorize(http.MethodPost, "/api/alertmanager/{DatasourceID}/api/v2/alerts"), metrics.Instrument( http.MethodPost, - "/api/alertmanager/{Recipient}/api/v2/alerts", + "/api/alertmanager/{DatasourceID}/api/v2/alerts", srv.RoutePostAMAlerts, m, ), ) group.Post( - toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), - api.authorize(http.MethodPost, "/api/alertmanager/{Recipient}/config/api/v1/alerts"), + toMacaronPath("/api/alertmanager/{DatasourceID}/config/api/v1/alerts"), + api.authorize(http.MethodPost, "/api/alertmanager/{DatasourceID}/config/api/v1/alerts"), metrics.Instrument( http.MethodPost, - "/api/alertmanager/{Recipient}/config/api/v1/alerts", + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts", srv.RoutePostAlertingConfig, m, ), @@ -407,11 +407,11 @@ func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiForkingServi ), ) group.Post( - toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/receivers/test"), - api.authorize(http.MethodPost, "/api/alertmanager/{Recipient}/config/api/v1/receivers/test"), + toMacaronPath("/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test"), + api.authorize(http.MethodPost, "/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test"), metrics.Instrument( http.MethodPost, - "/api/alertmanager/{Recipient}/config/api/v1/receivers/test", + "/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test", srv.RoutePostTestReceivers, m, ), diff --git a/pkg/services/ngalert/api/generated_base_api_prometheus.go b/pkg/services/ngalert/api/generated_base_api_prometheus.go index 3b6656eafba..dc6c8327730 100644 --- a/pkg/services/ngalert/api/generated_base_api_prometheus.go +++ b/pkg/services/ngalert/api/generated_base_api_prometheus.go @@ -43,11 +43,11 @@ func (f *ForkedPrometheusApi) RouteGetRuleStatuses(ctx *models.ReqContext) respo func (api *API) RegisterPrometheusApiEndpoints(srv PrometheusApiForkingService, m *metrics.API) { api.RouteRegister.Group("", func(group routing.RouteRegister) { group.Get( - toMacaronPath("/api/prometheus/{Recipient}/api/v1/alerts"), - api.authorize(http.MethodGet, "/api/prometheus/{Recipient}/api/v1/alerts"), + toMacaronPath("/api/prometheus/{DatasourceID}/api/v1/alerts"), + api.authorize(http.MethodGet, "/api/prometheus/{DatasourceID}/api/v1/alerts"), metrics.Instrument( http.MethodGet, - "/api/prometheus/{Recipient}/api/v1/alerts", + "/api/prometheus/{DatasourceID}/api/v1/alerts", srv.RouteGetAlertStatuses, m, ), @@ -73,11 +73,11 @@ func (api *API) RegisterPrometheusApiEndpoints(srv PrometheusApiForkingService, ), ) group.Get( - toMacaronPath("/api/prometheus/{Recipient}/api/v1/rules"), - api.authorize(http.MethodGet, "/api/prometheus/{Recipient}/api/v1/rules"), + toMacaronPath("/api/prometheus/{DatasourceID}/api/v1/rules"), + api.authorize(http.MethodGet, "/api/prometheus/{DatasourceID}/api/v1/rules"), metrics.Instrument( http.MethodGet, - "/api/prometheus/{Recipient}/api/v1/rules", + "/api/prometheus/{DatasourceID}/api/v1/rules", srv.RouteGetRuleStatuses, m, ), diff --git a/pkg/services/ngalert/api/generated_base_api_ruler.go b/pkg/services/ngalert/api/generated_base_api_ruler.go index 7167ebbae2e..751646342fc 100644 --- a/pkg/services/ngalert/api/generated_base_api_ruler.go +++ b/pkg/services/ngalert/api/generated_base_api_ruler.go @@ -113,21 +113,21 @@ func (api *API) RegisterRulerApiEndpoints(srv RulerApiForkingService, m *metrics ), ) group.Delete( - toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), - api.authorize(http.MethodDelete, "/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), + toMacaronPath("/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}"), + api.authorize(http.MethodDelete, "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}"), metrics.Instrument( http.MethodDelete, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}", + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}", srv.RouteDeleteNamespaceRulesConfig, m, ), ) group.Delete( - toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), - api.authorize(http.MethodDelete, "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), + toMacaronPath("/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}"), + api.authorize(http.MethodDelete, "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}"), metrics.Instrument( http.MethodDelete, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}", + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}", srv.RouteDeleteRuleGroupConfig, m, ), @@ -163,31 +163,31 @@ func (api *API) RegisterRulerApiEndpoints(srv RulerApiForkingService, m *metrics ), ) group.Get( - toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), - api.authorize(http.MethodGet, "/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), + toMacaronPath("/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}"), + api.authorize(http.MethodGet, "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}"), metrics.Instrument( http.MethodGet, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}", + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}", srv.RouteGetNamespaceRulesConfig, m, ), ) group.Get( - toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), - api.authorize(http.MethodGet, "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), + toMacaronPath("/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}"), + api.authorize(http.MethodGet, "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}"), metrics.Instrument( http.MethodGet, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}", + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}", srv.RouteGetRulegGroupConfig, m, ), ) group.Get( - toMacaronPath("/api/ruler/{Recipient}/api/v1/rules"), - api.authorize(http.MethodGet, "/api/ruler/{Recipient}/api/v1/rules"), + toMacaronPath("/api/ruler/{DatasourceID}/api/v1/rules"), + api.authorize(http.MethodGet, "/api/ruler/{DatasourceID}/api/v1/rules"), metrics.Instrument( http.MethodGet, - "/api/ruler/{Recipient}/api/v1/rules", + "/api/ruler/{DatasourceID}/api/v1/rules", srv.RouteGetRulesConfig, m, ), @@ -203,11 +203,11 @@ func (api *API) RegisterRulerApiEndpoints(srv RulerApiForkingService, m *metrics ), ) group.Post( - toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), - api.authorize(http.MethodPost, "/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), + toMacaronPath("/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}"), + api.authorize(http.MethodPost, "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}"), metrics.Instrument( http.MethodPost, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}", + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}", srv.RoutePostNameRulesConfig, m, ), diff --git a/pkg/services/ngalert/api/generated_base_api_testing.go b/pkg/services/ngalert/api/generated_base_api_testing.go index 9f13b74dc20..8e05bf5ff20 100644 --- a/pkg/services/ngalert/api/generated_base_api_testing.go +++ b/pkg/services/ngalert/api/generated_base_api_testing.go @@ -62,11 +62,11 @@ func (api *API) RegisterTestingApiEndpoints(srv TestingApiForkingService, m *met ), ) group.Post( - toMacaronPath("/api/v1/rule/test/{Recipient}"), - api.authorize(http.MethodPost, "/api/v1/rule/test/{Recipient}"), + toMacaronPath("/api/v1/rule/test/{DatasourceID}"), + api.authorize(http.MethodPost, "/api/v1/rule/test/{DatasourceID}"), metrics.Instrument( http.MethodPost, - "/api/v1/rule/test/{Recipient}", + "/api/v1/rule/test/{DatasourceID}", srv.RouteTestRuleConfig, m, ), diff --git a/pkg/services/ngalert/api/lotex_am.go b/pkg/services/ngalert/api/lotex_am.go index f626cfdb43e..2eb5ff18000 100644 --- a/pkg/services/ngalert/api/lotex_am.go +++ b/pkg/services/ngalert/api/lotex_am.go @@ -60,12 +60,12 @@ func (am *LotexAM) withAMReq( extractor func(*response.NormalResponse) (interface{}, error), headers map[string]string, ) response.Response { - recipient, err := strconv.ParseInt(web.Params(ctx.Req)[":Recipient"], 10, 64) + datasourceID, err := strconv.ParseInt(web.Params(ctx.Req)[":DatasourceID"], 10, 64) if err != nil { - return response.Error(http.StatusBadRequest, "Recipient is invalid", err) + return response.Error(http.StatusBadRequest, "DatasourceID is invalid", err) } - ds, err := am.DataProxy.DataSourceCache.GetDatasource(ctx.Req.Context(), recipient, ctx.SignedInUser, ctx.SkipCache) + ds, err := am.DataProxy.DataSourceCache.GetDatasource(ctx.Req.Context(), datasourceID, ctx.SignedInUser, ctx.SkipCache) if err != nil { if errors.Is(err, models.ErrDataSourceAccessDenied) { return ErrResp(http.StatusForbidden, err, "Access denied to datasource") diff --git a/pkg/services/ngalert/api/lotex_prom.go b/pkg/services/ngalert/api/lotex_prom.go index d2c2efbed3d..011885e309d 100644 --- a/pkg/services/ngalert/api/lotex_prom.go +++ b/pkg/services/ngalert/api/lotex_prom.go @@ -78,12 +78,12 @@ func (p *LotexProm) RouteGetRuleStatuses(ctx *models.ReqContext) response.Respon } func (p *LotexProm) getEndpoints(ctx *models.ReqContext) (*promEndpoints, error) { - recipient, err := strconv.ParseInt(web.Params(ctx.Req)[":Recipient"], 10, 64) + datasourceID, err := strconv.ParseInt(web.Params(ctx.Req)[":DatasourceID"], 10, 64) if err != nil { - return nil, fmt.Errorf("recipient is invalid") + return nil, fmt.Errorf("datasource ID is invalid") } - ds, err := p.DataProxy.DataSourceCache.GetDatasource(ctx.Req.Context(), recipient, ctx.SignedInUser, ctx.SkipCache) + ds, err := p.DataProxy.DataSourceCache.GetDatasource(ctx.Req.Context(), datasourceID, ctx.SignedInUser, ctx.SkipCache) if err != nil { return nil, err } diff --git a/pkg/services/ngalert/api/lotex_ruler.go b/pkg/services/ngalert/api/lotex_ruler.go index 8c71f8dd962..efdfbd564b7 100644 --- a/pkg/services/ngalert/api/lotex_ruler.go +++ b/pkg/services/ngalert/api/lotex_ruler.go @@ -176,12 +176,12 @@ func (r *LotexRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimo } func (r *LotexRuler) validateAndGetPrefix(ctx *models.ReqContext) (string, error) { - recipient, err := strconv.ParseInt(web.Params(ctx.Req)[":Recipient"], 10, 64) + datasourceID, err := strconv.ParseInt(web.Params(ctx.Req)[":DatasourceID"], 10, 64) if err != nil { - return "", fmt.Errorf("recipient is invalid") + return "", fmt.Errorf("datasource ID is invalid") } - ds, err := r.DataProxy.DataSourceCache.GetDatasource(ctx.Req.Context(), recipient, ctx.SignedInUser, ctx.SkipCache) + ds, err := r.DataProxy.DataSourceCache.GetDatasource(ctx.Req.Context(), datasourceID, ctx.SignedInUser, ctx.SkipCache) if err != nil { return "", err } diff --git a/pkg/services/ngalert/api/lotex_ruler_test.go b/pkg/services/ngalert/api/lotex_ruler_test.go index cc7d1d27c70..d1afa5a946a 100644 --- a/pkg/services/ngalert/api/lotex_ruler_test.go +++ b/pkg/services/ngalert/api/lotex_ruler_test.go @@ -25,64 +25,64 @@ func TestLotexRuler_ValidateAndGetPrefix(t *testing.T) { err error }{ { - name: "with an invalid recipient", - namedParams: map[string]string{":Recipient": "AAABBB"}, - err: errors.New("recipient is invalid"), + name: "with an invalid datasource ID", + namedParams: map[string]string{":DatasourceID": "AAABBB"}, + err: errors.New("datasource ID is invalid"), }, { name: "with an error while trying to fetch the datasource", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, datasourceCache: fakeCacheService{err: models.ErrDataSourceNotFound}, err: errors.New("data source not found"), }, { name: "with an empty datasource URL", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, datasourceCache: fakeCacheService{datasource: &models.DataSource{}}, err: errors.New("URL for this data source is empty"), }, { name: "with an unsupported datasource type", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com"}}, err: errors.New("unexpected datasource type. expecting loki or prometheus"), }, { name: "with a Loki datasource", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com", Type: LokiDatasourceType}}, expected: "/api/prom/rules", }, { name: "with a Prometheus datasource", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com", Type: PrometheusDatasourceType}}, expected: "/rules", }, { name: "with a Prometheus datasource and subtype of Cortex", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, urlParams: "?subtype=cortex", datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com", Type: PrometheusDatasourceType}}, expected: "/rules", }, { name: "with a Prometheus datasource and subtype of Mimir", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, urlParams: "?subtype=mimir", datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com", Type: PrometheusDatasourceType}}, expected: "/config/v1/rules", }, { name: "with a Prometheus datasource and subtype of Prometheus", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, urlParams: "?subtype=prometheus", datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com", Type: PrometheusDatasourceType}}, expected: "/rules", }, { name: "with a Prometheus datasource and no subtype", - namedParams: map[string]string{":Recipient": "164"}, + namedParams: map[string]string{":DatasourceID": "164"}, datasourceCache: fakeCacheService{datasource: &models.DataSource{Url: "http://loki.com", Type: PrometheusDatasourceType}}, expected: "/rules", }, diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index ad75f5215b3..a2cd6802df3 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -31,7 +31,7 @@ import ( // 201: Ack // 400: ValidationError -// swagger:route POST /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RoutePostAlertingConfig +// swagger:route POST /api/alertmanager/{DatasourceID}/config/api/v1/alerts alertmanager RoutePostAlertingConfig // // sets an Alerting config // @@ -47,7 +47,7 @@ import ( // 200: GettableUserConfig // 400: ValidationError -// swagger:route GET /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RouteGetAlertingConfig +// swagger:route GET /api/alertmanager/{DatasourceID}/config/api/v1/alerts alertmanager RouteGetAlertingConfig // // gets an Alerting config // @@ -63,7 +63,7 @@ import ( // 200: Ack // 400: ValidationError -// swagger:route DELETE /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RouteDeleteAlertingConfig +// swagger:route DELETE /api/alertmanager/{DatasourceID}/config/api/v1/alerts alertmanager RouteDeleteAlertingConfig // // deletes the Alerting config for a tenant // @@ -79,7 +79,7 @@ import ( // 200: GettableStatus // 400: ValidationError -// swagger:route GET /api/alertmanager/{Recipient}/api/v2/status alertmanager RouteGetAMStatus +// swagger:route GET /api/alertmanager/{DatasourceID}/api/v2/status alertmanager RouteGetAMStatus // // get alertmanager status and configuration // @@ -95,7 +95,7 @@ import ( // 200: gettableAlerts // 400: ValidationError -// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RouteGetAMAlerts +// swagger:route GET /api/alertmanager/{DatasourceID}/api/v2/alerts alertmanager RouteGetAMAlerts // // get alertmanager alerts // @@ -111,7 +111,7 @@ import ( // 200: Ack // 400: ValidationError -// swagger:route POST /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RoutePostAMAlerts +// swagger:route POST /api/alertmanager/{DatasourceID}/api/v2/alerts alertmanager RoutePostAMAlerts // // create alertmanager alerts // @@ -127,7 +127,7 @@ import ( // 200: alertGroups // 400: ValidationError -// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts/groups alertmanager RouteGetAMAlertGroups +// swagger:route GET /api/alertmanager/{DatasourceID}/api/v2/alerts/groups alertmanager RouteGetAMAlertGroups // // get alertmanager alerts // @@ -149,7 +149,7 @@ import ( // 408: Failure // 409: AlertManagerNotReady -// swagger:route POST /api/alertmanager/{Recipient}/config/api/v1/receivers/test alertmanager RoutePostTestReceivers +// swagger:route POST /api/alertmanager/{DatasourceID}/config/api/v1/receivers/test alertmanager RoutePostTestReceivers // // Test Grafana managed receivers without saving them. // @@ -171,7 +171,7 @@ import ( // 200: gettableSilences // 400: ValidationError -// swagger:route GET /api/alertmanager/{Recipient}/api/v2/silences alertmanager RouteGetSilences +// swagger:route GET /api/alertmanager/{DatasourceID}/api/v2/silences alertmanager RouteGetSilences // // get silences // @@ -187,7 +187,7 @@ import ( // 201: gettableSilence // 400: ValidationError -// swagger:route POST /api/alertmanager/{Recipient}/api/v2/silences alertmanager RouteCreateSilence +// swagger:route POST /api/alertmanager/{DatasourceID}/api/v2/silences alertmanager RouteCreateSilence // // create silence // @@ -203,7 +203,7 @@ import ( // 200: gettableSilence // 400: ValidationError -// swagger:route GET /api/alertmanager/{Recipient}/api/v2/silence/{SilenceId} alertmanager RouteGetSilence +// swagger:route GET /api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId} alertmanager RouteGetSilence // // get silence // @@ -219,7 +219,7 @@ import ( // 200: Ack // 400: ValidationError -// swagger:route DELETE /api/alertmanager/{Recipient}/api/v2/silence/{SilenceId} alertmanager RouteDeleteSilence +// swagger:route DELETE /api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId} alertmanager RouteDeleteSilence // // delete silence // @@ -448,9 +448,9 @@ type BodyAlertingConfig struct { // testing routes // swagger:parameters RouteTestReceiverConfig RouteTestRuleConfig type DatasourceReference struct { - // Recipient should be the numeric datasource id + // DatasourceID should be the numeric datasource id // in:path - Recipient int + DatasourceID int } // swagger:model diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go index f8404905e9d..bbc08c9103b 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go @@ -21,7 +21,7 @@ import ( // 202: NamespaceConfigResponse // -// swagger:route Get /api/ruler/{Recipient}/api/v1/rules ruler RouteGetRulesConfig +// swagger:route Get /api/ruler/{DatasourceID}/api/v1/rules ruler RouteGetRulesConfig // // List rule groups // @@ -43,7 +43,7 @@ import ( // 202: Ack // -// swagger:route POST /api/ruler/{Recipient}/api/v1/rules/{Namespace} ruler RoutePostNameRulesConfig +// swagger:route POST /api/ruler/{DatasourceID}/api/v1/rules/{Namespace} ruler RoutePostNameRulesConfig // // Creates or updates a rule group // @@ -64,7 +64,7 @@ import ( // Responses: // 202: NamespaceConfigResponse -// swagger:route Get /api/ruler/{Recipient}/api/v1/rules/{Namespace} ruler RouteGetNamespaceRulesConfig +// swagger:route Get /api/ruler/{DatasourceID}/api/v1/rules/{Namespace} ruler RouteGetNamespaceRulesConfig // // Get rule groups by namespace // @@ -81,7 +81,7 @@ import ( // Responses: // 202: Ack -// swagger:route Delete /api/ruler/{Recipient}/api/v1/rules/{Namespace} ruler RouteDeleteNamespaceRulesConfig +// swagger:route Delete /api/ruler/{DatasourceID}/api/v1/rules/{Namespace} ruler RouteDeleteNamespaceRulesConfig // // Delete namespace // @@ -98,7 +98,7 @@ import ( // Responses: // 202: RuleGroupConfigResponse -// swagger:route Get /api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname} ruler RouteGetRulegGroupConfig +// swagger:route Get /api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname} ruler RouteGetRulegGroupConfig // // Get rule group // @@ -115,7 +115,7 @@ import ( // Responses: // 202: Ack -// swagger:route Delete /api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname} ruler RouteDeleteRuleGroupConfig +// swagger:route Delete /api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname} ruler RouteDeleteRuleGroupConfig // // Delete rule group // diff --git a/pkg/services/ngalert/api/tooling/definitions/prom.go b/pkg/services/ngalert/api/tooling/definitions/prom.go index 48fdeec8e7a..1ba9f835178 100644 --- a/pkg/services/ngalert/api/tooling/definitions/prom.go +++ b/pkg/services/ngalert/api/tooling/definitions/prom.go @@ -13,7 +13,7 @@ import ( // Responses: // 200: RuleResponse -// swagger:route GET /api/prometheus/{Recipient}/api/v1/rules prometheus RouteGetRuleStatuses +// swagger:route GET /api/prometheus/{DatasourceID}/api/v1/rules prometheus RouteGetRuleStatuses // // gets the evaluation statuses of all rules // @@ -27,7 +27,7 @@ import ( // Responses: // 200: AlertResponse -// swagger:route GET /api/prometheus/{Recipient}/api/v1/alerts prometheus RouteGetAlertStatuses +// swagger:route GET /api/prometheus/{DatasourceID}/api/v1/alerts prometheus RouteGetAlertStatuses // // gets the current alerts // diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go index 3d86af76928..ddbcc007f2c 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go @@ -66,7 +66,7 @@ type EmbeddedContactPoint struct { Type string `json:"type" binding:"required"` Settings *simplejson.Json `json:"settings" binding:"required"` DisableResolveMessage bool `json:"disableResolveMessage"` - Provenance string `json:"provanance"` + Provenance string `json:"provenance"` } const RedactedValue = "[REDACTED]" diff --git a/pkg/services/ngalert/api/tooling/definitions/testing.go b/pkg/services/ngalert/api/tooling/definitions/testing.go index b2c4ffa8416..dfa2e88ee2b 100644 --- a/pkg/services/ngalert/api/tooling/definitions/testing.go +++ b/pkg/services/ngalert/api/tooling/definitions/testing.go @@ -26,7 +26,7 @@ import ( // Responses: // 200: TestRuleResponse -// swagger:route Post /api/v1/rule/test/{Recipient} testing RouteTestRuleConfig +// swagger:route Post /api/v1/rule/test/{DatasourceID} testing RouteTestRuleConfig // // Test a rule against external data source ruler // diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index f9373171bf5..b5bb902ac09 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -8,6 +8,32 @@ "type": "object", "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "AddApiKeyCommand": { + "description": "COMMANDS", + "properties": { + "name": { + "type": "string", + "x-go-name": "Name" + }, + "role": { + "enum": [ + "Viewer", + "Editor", + "Admin" + ], + "type": "string", + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" + }, + "secondsToLive": { + "format": "int64", + "type": "integer", + "x-go-name": "SecondsToLive" + } + }, + "type": "object", + "x-go-package": "github.com/grafana/grafana/pkg/models" + }, "Alert": { "properties": { "activeAt": { @@ -566,6 +592,7 @@ "x-go-name": "DisableResolveMessage" }, "name": { + "description": "Name is used as grouping key in the UI. Contact points with the\nsame name will be grouped in the UI.", "type": "string", "x-go-name": "Name" }, @@ -581,6 +608,7 @@ "x-go-name": "Type" }, "uid": { + "description": "UID is the unique identifier of the contact point. This will be\nautomatically set be the Grafana.", "type": "string", "x-go-name": "UID" } @@ -1963,6 +1991,10 @@ "type": "object", "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "Provenance": { + "type": "string", + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models" + }, "PushoverConfig": { "properties": { "expire": { @@ -2164,6 +2196,9 @@ "object_matchers": { "$ref": "#/definitions/ObjectMatchers" }, + "provenance": { + "$ref": "#/definitions/Provenance" + }, "receiver": { "type": "string", "x-go-name": "Receiver" @@ -3064,7 +3099,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { - "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -3086,14 +3120,17 @@ "labels", "receiver" ], - "type": "object" + "type": "object", + "x-go-name": "AlertGroup", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroups": { - "description": "AlertGroups alert groups", "items": { "$ref": "#/definitions/alertGroup" }, - "type": "array" + "type": "array", + "x-go-name": "AlertGroups", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertStatus": { "description": "AlertStatus alert status", @@ -3213,6 +3250,7 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { + "description": "GettableAlert gettable alert", "properties": { "annotations": { "$ref": "#/definitions/labelSet" @@ -3271,9 +3309,7 @@ "status", "updatedAt" ], - "type": "object", - "x-go-name": "GettableAlert", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + "type": "object" }, "gettableAlerts": { "description": "GettableAlerts gettable alerts", @@ -3283,7 +3319,6 @@ "type": "array" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "properties": { "comment": { "description": "comment", @@ -3335,14 +3370,17 @@ "status", "updatedAt" ], - "type": "object" + "type": "object", + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence" }, - "type": "array" + "type": "array", + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "labelSet": { "additionalProperties": { @@ -3515,6 +3553,7 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { + "description": "Receiver receiver", "properties": { "name": { "description": "name", @@ -3525,9 +3564,7 @@ "required": [ "name" ], - "type": "object", - "x-go-name": "Receiver", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + "type": "object" }, "silence": { "description": "Silence silence", @@ -4089,7 +4126,7 @@ ] } }, - "/api/alertmanager/{Recipient}/api/v2/alerts": { + "/api/alertmanager/{DatasourceID}/api/v2/alerts": { "get": { "description": "get alertmanager alerts", "operationId": "RouteGetAMAlerts", @@ -4136,10 +4173,10 @@ "x-go-name": "Receivers" }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4177,10 +4214,10 @@ } }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4204,7 +4241,7 @@ ] } }, - "/api/alertmanager/{Recipient}/api/v2/alerts/groups": { + "/api/alertmanager/{DatasourceID}/api/v2/alerts/groups": { "get": { "description": "get alertmanager alerts", "operationId": "RouteGetAMAlertGroups", @@ -4251,10 +4288,10 @@ "x-go-name": "Receivers" }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4278,7 +4315,7 @@ ] } }, - "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}": { + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}": { "delete": { "description": "delete silence", "operationId": "RouteDeleteSilence", @@ -4290,10 +4327,10 @@ "type": "string" }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4327,10 +4364,10 @@ "type": "string" }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4354,7 +4391,7 @@ ] } }, - "/api/alertmanager/{Recipient}/api/v2/silences": { + "/api/alertmanager/{DatasourceID}/api/v2/silences": { "get": { "description": "get silences", "operationId": "RouteGetSilences", @@ -4369,10 +4406,10 @@ "x-go-name": "Filter" }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4407,10 +4444,10 @@ } }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4434,16 +4471,16 @@ ] } }, - "/api/alertmanager/{Recipient}/api/v2/status": { + "/api/alertmanager/{DatasourceID}/api/v2/status": { "get": { "description": "get alertmanager status and configuration", "operationId": "RouteGetAMStatus", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4467,16 +4504,16 @@ ] } }, - "/api/alertmanager/{Recipient}/config/api/v1/alerts": { + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts": { "delete": { "description": "deletes the Alerting config for a tenant", "operationId": "RouteDeleteAlertingConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4504,10 +4541,10 @@ "operationId": "RouteGetAlertingConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4542,10 +4579,10 @@ } }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4569,7 +4606,7 @@ ] } }, - "/api/alertmanager/{Recipient}/config/api/v1/receivers/test": { + "/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test": { "post": { "operationId": "RoutePostTestReceivers", "parameters": [ @@ -4581,10 +4618,10 @@ } }, { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4706,16 +4743,16 @@ ] } }, - "/api/prometheus/{Recipient}/api/v1/alerts": { + "/api/prometheus/{DatasourceID}/api/v1/alerts": { "get": { "description": "gets the current alerts", "operationId": "RouteGetAlertStatuses", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -4733,16 +4770,16 @@ ] } }, - "/api/prometheus/{Recipient}/api/v1/rules": { + "/api/prometheus/{DatasourceID}/api/v1/rules": { "get": { "description": "gets the evaluation statuses of all rules", "operationId": "RouteGetRuleStatuses", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" } @@ -5100,16 +5137,16 @@ ] } }, - "/api/ruler/{Recipient}/api/v1/rules": { + "/api/ruler/{DatasourceID}/api/v1/rules": { "get": { "description": "List rule groups", "operationId": "RouteGetRulesConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, @@ -5141,16 +5178,16 @@ ] } }, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}": { + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}": { "delete": { "description": "Delete namespace", "operationId": "RouteDeleteNamespaceRulesConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, @@ -5178,10 +5215,10 @@ "operationId": "RouteGetNamespaceRulesConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, @@ -5216,10 +5253,10 @@ "operationId": "RoutePostNameRulesConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, @@ -5250,16 +5287,16 @@ ] } }, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}": { + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}": { "delete": { "description": "Delete rule group", "operationId": "RouteDeleteRuleGroupConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, @@ -5293,10 +5330,10 @@ "operationId": "RouteGetRulegGroupConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, @@ -5502,7 +5539,7 @@ ] } }, - "/api/v1/rule/test/{Recipient}": { + "/api/v1/rule/test/{DatasourceID}": { "post": { "consumes": [ "application/json" @@ -5511,10 +5548,10 @@ "operationId": "RouteTestRuleConfig", "parameters": [ { - "description": "Recipient should be the numeric datasource id", + "description": "DatasourceID should be the numeric datasource id", "format": "int64", "in": "path", - "name": "Recipient", + "name": "DatasourceID", "required": true, "type": "integer" }, diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 188a5c4b885..dc24be80c29 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -465,7 +465,7 @@ } } }, - "/api/alertmanager/{Recipient}/api/v2/alerts": { + "/api/alertmanager/{DatasourceID}/api/v2/alerts": { "get": { "description": "get alertmanager alerts", "tags": [ @@ -517,8 +517,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -558,8 +558,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -580,7 +580,7 @@ } } }, - "/api/alertmanager/{Recipient}/api/v2/alerts/groups": { + "/api/alertmanager/{DatasourceID}/api/v2/alerts/groups": { "get": { "description": "get alertmanager alerts", "tags": [ @@ -632,8 +632,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -654,7 +654,7 @@ } } }, - "/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}": { + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}": { "get": { "description": "get silence", "tags": [ @@ -671,8 +671,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -708,8 +708,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -730,7 +730,7 @@ } } }, - "/api/alertmanager/{Recipient}/api/v2/silences": { + "/api/alertmanager/{DatasourceID}/api/v2/silences": { "get": { "description": "get silences", "tags": [ @@ -750,8 +750,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -788,8 +788,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -810,7 +810,7 @@ } } }, - "/api/alertmanager/{Recipient}/api/v2/status": { + "/api/alertmanager/{DatasourceID}/api/v2/status": { "get": { "description": "get alertmanager status and configuration", "tags": [ @@ -821,8 +821,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -843,7 +843,7 @@ } } }, - "/api/alertmanager/{Recipient}/config/api/v1/alerts": { + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts": { "get": { "description": "gets an Alerting config", "tags": [ @@ -854,8 +854,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -892,8 +892,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -923,8 +923,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -945,7 +945,7 @@ } } }, - "/api/alertmanager/{Recipient}/config/api/v1/receivers/test": { + "/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test": { "post": { "tags": [ "alertmanager" @@ -963,8 +963,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -1082,7 +1082,7 @@ } } }, - "/api/prometheus/{Recipient}/api/v1/alerts": { + "/api/prometheus/{DatasourceID}/api/v1/alerts": { "get": { "description": "gets the current alerts", "tags": [ @@ -1093,8 +1093,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -1109,7 +1109,7 @@ } } }, - "/api/prometheus/{Recipient}/api/v1/rules": { + "/api/prometheus/{DatasourceID}/api/v1/rules": { "get": { "description": "gets the evaluation statuses of all rules", "tags": [ @@ -1120,8 +1120,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -1476,7 +1476,7 @@ } } }, - "/api/ruler/{Recipient}/api/v1/rules": { + "/api/ruler/{DatasourceID}/api/v1/rules": { "get": { "description": "List rule groups", "produces": [ @@ -1490,8 +1490,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1517,7 +1517,7 @@ } } }, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}": { + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}": { "get": { "description": "Get rule groups by namespace", "produces": [ @@ -1531,8 +1531,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1566,8 +1566,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1604,8 +1604,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1626,7 +1626,7 @@ } } }, - "/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}": { + "/api/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}": { "get": { "description": "Get rule group", "produces": [ @@ -1640,8 +1640,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1677,8 +1677,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1878,7 +1878,7 @@ } } }, - "/api/v1/rule/test/{Recipient}": { + "/api/v1/rule/test/{DatasourceID}": { "post": { "description": "Test a rule against external data source ruler", "consumes": [ @@ -1895,8 +1895,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -1924,6 +1924,32 @@ "type": "object", "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "AddApiKeyCommand": { + "description": "COMMANDS", + "type": "object", + "properties": { + "name": { + "type": "string", + "x-go-name": "Name" + }, + "role": { + "type": "string", + "enum": [ + "Viewer", + "Editor", + "Admin" + ], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" + }, + "secondsToLive": { + "type": "integer", + "format": "int64", + "x-go-name": "SecondsToLive" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/models" + }, "Alert": { "type": "object", "title": "Alert has info for an alert.", @@ -2484,6 +2510,7 @@ "x-go-name": "DisableResolveMessage" }, "name": { + "description": "Name is used as grouping key in the UI. Contact points with the\nsame name will be grouped in the UI.", "type": "string", "x-go-name": "Name" }, @@ -2499,6 +2526,7 @@ "x-go-name": "Type" }, "uid": { + "description": "UID is the unique identifier of the contact point. This will be\nautomatically set be the Grafana.", "type": "string", "x-go-name": "UID" } @@ -3883,6 +3911,10 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "Provenance": { + "type": "string", + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models" + }, "PushoverConfig": { "type": "object", "properties": { @@ -4085,6 +4117,9 @@ "object_matchers": { "$ref": "#/definitions/ObjectMatchers" }, + "provenance": { + "$ref": "#/definitions/Provenance" + }, "receiver": { "type": "string", "x-go-name": "Receiver" @@ -4984,7 +5019,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { - "description": "AlertGroup alert group", "type": "object", "required": [ "alerts", @@ -5007,14 +5041,17 @@ "$ref": "#/definitions/receiver" } }, + "x-go-name": "AlertGroup", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/alertGroup" }, "alertGroups": { - "description": "AlertGroups alert groups", "type": "array", "items": { "$ref": "#/definitions/alertGroup" }, + "x-go-name": "AlertGroups", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/alertGroups" }, "alertStatus": { @@ -5135,6 +5172,7 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { + "description": "GettableAlert gettable alert", "type": "object", "required": [ "labels", @@ -5194,8 +5232,6 @@ "x-go-name": "UpdatedAt" } }, - "x-go-name": "GettableAlert", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableAlert" }, "gettableAlerts": { @@ -5207,7 +5243,6 @@ "$ref": "#/definitions/gettableAlerts" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "type": "object", "required": [ "comment", @@ -5260,14 +5295,17 @@ "x-go-name": "UpdatedAt" } }, + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableSilence" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "$ref": "#/definitions/gettableSilence" }, + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableSilences" }, "labelSet": { @@ -5442,6 +5480,7 @@ "$ref": "#/definitions/postableSilence" }, "receiver": { + "description": "Receiver receiver", "type": "object", "required": [ "name" @@ -5453,8 +5492,6 @@ "x-go-name": "Name" } }, - "x-go-name": "Receiver", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/receiver" }, "silence": { diff --git a/pkg/services/ngalert/api/util.go b/pkg/services/ngalert/api/util.go index 76808313bae..ce5e8c1ef24 100644 --- a/pkg/services/ngalert/api/util.go +++ b/pkg/services/ngalert/api/util.go @@ -36,8 +36,8 @@ func toMacaronPath(path string) string { } func backendType(ctx *models.ReqContext, cache datasources.CacheService) (apimodels.Backend, error) { - recipient := web.Params(ctx.Req)[":Recipient"] - if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil { + datasourceID := web.Params(ctx.Req)[":DatasourceID"] + if datasourceID, err := strconv.ParseInt(datasourceID, 10, 64); err == nil { if ds, err := cache.GetDatasource(ctx.Req.Context(), datasourceID, ctx.SignedInUser, ctx.SkipCache); err == nil { switch ds.Type { case "loki", "prometheus": @@ -49,7 +49,7 @@ func backendType(ctx *models.ReqContext, cache datasources.CacheService) (apimod } } } - return 0, fmt.Errorf("unexpected backend type (%v)", recipient) + return 0, fmt.Errorf("unexpected backend type (%v)", datasourceID) } // macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic. @@ -98,12 +98,12 @@ func (p *AlertingProxy) withReq( newCtx, resp := replacedResponseWriter(ctx) newCtx.Req = req - recipient, err := strconv.ParseInt(web.Params(ctx.Req)[":Recipient"], 10, 64) + datasourceID, err := strconv.ParseInt(web.Params(ctx.Req)[":DatasourceID"], 10, 64) if err != nil { - return ErrResp(http.StatusBadRequest, err, "Recipient is invalid") + return ErrResp(http.StatusBadRequest, err, "DatasourceID is invalid") } - p.DataProxy.ProxyDatasourceRequestWithID(newCtx, recipient) + p.DataProxy.ProxyDatasourceRequestWithID(newCtx, datasourceID) status := resp.Status() if status >= 400 { diff --git a/pkg/services/ngalert/api/util_test.go b/pkg/services/ngalert/api/util_test.go index a34f482fa84..21035f83e0d 100644 --- a/pkg/services/ngalert/api/util_test.go +++ b/pkg/services/ngalert/api/util_test.go @@ -16,8 +16,8 @@ func TestToMacaronPath(t *testing.T) { expectedOutputPath: "", }, { - inputPath: "/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}", - expectedOutputPath: "/ruler/:Recipient/api/v1/rules/:Namespace/:Groupname", + inputPath: "/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}", + expectedOutputPath: "/ruler/:DatasourceID/api/v1/rules/:Namespace/:Groupname", }, } for _, tc := range testCases { diff --git a/pkg/services/ngalert/metrics/ngalert.go b/pkg/services/ngalert/metrics/ngalert.go index bb89cfc2b2b..6bbf6a601b9 100644 --- a/pkg/services/ngalert/metrics/ngalert.go +++ b/pkg/services/ngalert/metrics/ngalert.go @@ -7,15 +7,17 @@ import ( "sync" "time" - "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/models" - apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - - "github.com/grafana/grafana/pkg/web" "github.com/prometheus/alertmanager/api/metrics" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/models" + legacyMetrics "github.com/grafana/grafana/pkg/services/alerting/metrics" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + + "github.com/grafana/grafana/pkg/web" ) const ( @@ -52,6 +54,7 @@ type Scheduler struct { EvalDuration *prometheus.SummaryVec GetAlertRulesDuration prometheus.Histogram SchedulePeriodicDuration prometheus.Histogram + Ticker *legacyMetrics.Ticker } type MultiOrgAlertmanager struct { @@ -179,6 +182,7 @@ func newSchedulerMetrics(r prometheus.Registerer) *Scheduler { Buckets: []float64{0.1, 0.25, 0.5, 1, 2, 5, 10}, }, ), + Ticker: legacyMetrics.NewTickerMetrics(r), } } @@ -292,8 +296,8 @@ func Instrument( // TODO: We could look up the datasource type via our datasource service var backend string - recipient := web.Params(c.Req)[":Recipient"] - if recipient == apimodels.GrafanaBackend.String() || recipient == "" { + datasourceID := web.Params(c.Req)[":DatasourceID"] + if datasourceID == apimodels.GrafanaBackend.String() || datasourceID == "" { backend = GrafanaBackend } else { backend = ProxyBackend diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 8da7640f363..6bf3c088421 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -232,6 +232,7 @@ type ListAlertRulesQuery struct { OrgID int64 NamespaceUIDs []string ExcludeOrgs []int64 + RuleGroup string // DashboardUID and PanelID are optional and allow filtering rules // to return just those for a dashboard and panel. @@ -250,22 +251,14 @@ type ListNamespaceAlertRulesQuery struct { Result []*AlertRule } -// GetAlertRulesQuery is the query for listing rule group alert rules -type GetAlertRulesQuery struct { - OrgID int64 - // Namespace is the folder slug - NamespaceUID string - RuleGroup *string - - // DashboardUID and PanelID are optional and allow filtering rules - // to return just those for a dashboard and panel. - DashboardUID string - PanelID int64 - - Result []*AlertRule +// ListRuleGroupsQuery is the query for listing unique rule groups +// across all organizations +type ListRuleGroupsQuery struct { + Result []string } // ListOrgRuleGroupsQuery is the query for listing unique rule groups +// for an organization type ListOrgRuleGroupsQuery struct { OrgID int64 NamespaceUIDs []string diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 49158bc52b5..b47cc404a33 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -99,7 +99,7 @@ func (ng *AlertNG) init() error { decryptFn := ng.SecretsService.GetDecryptedValue multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics() - ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, decryptFn, multiOrgMetrics, ng.NotificationService, log.New("ngalert.multiorg.alertmanager"), ng.SecretsService) + ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, store, decryptFn, multiOrgMetrics, ng.NotificationService, log.New("ngalert.multiorg.alertmanager"), ng.SecretsService) if err != nil { return err } diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go index 7efd6561589..f3d873a9c98 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config.go +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -5,8 +5,10 @@ import ( "errors" "fmt" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" ) @@ -81,6 +83,13 @@ func (moa *MultiOrgAlertmanager) GetAlertmanagerConfiguration(ctx context.Contex result.AlertmanagerConfig.Receivers = append(result.AlertmanagerConfig.Receivers, &gettableApiReceiver) } + if moa.settings.IsFeatureToggleEnabled(featuremgmt.FlagAlertProvisioning) { + result.AlertmanagerConfig, err = moa.mergeProvenance(ctx, result.AlertmanagerConfig, org) + if err != nil { + return definitions.GettableUserConfig{}, err + } + } + return result, nil } @@ -117,3 +126,18 @@ func (moa *MultiOrgAlertmanager) ApplyAlertmanagerConfiguration(ctx context.Cont return nil } + +func (moa *MultiOrgAlertmanager) mergeProvenance(ctx context.Context, config definitions.GettableApiAlertingConfig, org int64) (definitions.GettableApiAlertingConfig, error) { + if config.Route != nil { + adp := provisioning.ProvenanceOrgAdapter{ + Inner: config.Route, + OrgID: org, + } + provenance, err := moa.ProvStore.GetProvenance(ctx, adp) + if err != nil { + return definitions.GettableApiAlertingConfig{}, err + } + config.Route.Provenance = provenance + } + return config, nil +} diff --git a/pkg/services/ngalert/notifier/available_channels.go b/pkg/services/ngalert/notifier/available_channels.go index 65c52ff48e1..317b59db4bb 100644 --- a/pkg/services/ngalert/notifier/available_channels.go +++ b/pkg/services/ngalert/notifier/available_channels.go @@ -383,7 +383,7 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin { Description: "Specify channel, private group, or IM channel (can be an encoded ID or a name) - required unless you provide a webhook", PropertyName: "recipient", Required: true, - DependsOn: "secureSettings.url", + DependsOn: "url", }, // Logically, this field should be required when not using a webhook, since the Slack API needs a token. // However, since the UI doesn't allow to say that a field is required or not depending on another field, @@ -397,7 +397,7 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin { PropertyName: "token", Secure: true, Required: true, - DependsOn: "secureSettings.url", + DependsOn: "url", }, { Label: "Username", @@ -463,7 +463,7 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin { PropertyName: "url", Secure: true, Required: true, - DependsOn: "secureSettings.token", + DependsOn: "token", }, { // New in 8.4. Label: "Endpoint URL", diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index d34677c3e44..6764619121d 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -10,6 +10,7 @@ import ( "time" "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/secrets" @@ -30,7 +31,8 @@ var ( ) type MultiOrgAlertmanager struct { - Crypto Crypto + Crypto Crypto + ProvStore provisioning.ProvisioningStore alertmanagersMtx sync.RWMutex alertmanagers map[int64]*Alertmanager @@ -53,11 +55,12 @@ type MultiOrgAlertmanager struct { } func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, orgStore store.OrgStore, - kvStore kvstore.KVStore, decryptFn channels.GetDecryptedValueFn, m *metrics.MultiOrgAlertmanager, - ns notifications.Service, l log.Logger, s secrets.Service, + kvStore kvstore.KVStore, provStore provisioning.ProvisioningStore, decryptFn channels.GetDecryptedValueFn, + m *metrics.MultiOrgAlertmanager, ns notifications.Service, l log.Logger, s secrets.Service, ) (*MultiOrgAlertmanager, error) { moa := &MultiOrgAlertmanager{ - Crypto: NewCrypto(s, configStore, l), + Crypto: NewCrypto(s, configStore, l), + ProvStore: provStore, logger: l, settings: cfg, diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go index 56899182532..d0f819775ff 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/secrets/fakes" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/setting" @@ -32,6 +33,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { tmpDir := t.TempDir() kvStore := NewFakeKVStore(t) + provStore := provisioning.NewFakeProvisioningStore() secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue reg := prometheus.NewPedanticRegistry() @@ -44,7 +46,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { DisabledOrgs: map[int64]struct{}{5: {}}, }, // do not poll in tests. } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) ctx := context.Background() @@ -157,6 +159,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) tmpDir := t.TempDir() kvStore := NewFakeKVStore(t) + provStore := provisioning.NewFakeProvisioningStore() secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue reg := prometheus.NewPedanticRegistry() @@ -168,7 +171,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(), }, // do not poll in tests. } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) ctx := context.Background() @@ -214,11 +217,12 @@ func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) { UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration()}, // do not poll in tests. } kvStore := NewFakeKVStore(t) + provStore := provisioning.NewFakeProvisioningStore() secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue reg := prometheus.NewPedanticRegistry() m := metrics.NewNGAlert(reg) - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) ctx := context.Background() diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 00b1b60aba7..83922021f0c 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -177,9 +177,9 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in if err != nil { return err } - adapter := provenanceOrgAdapter{ - inner: &contactPoint, - orgID: orgID, + adapter := ProvenanceOrgAdapter{ + Inner: &contactPoint, + OrgID: orgID, } err = ecp.provenanceStore.SetProvenance(ctx, adapter, provenance) if err != nil { @@ -218,9 +218,9 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in return err } // check that provenance is not changed in a invalid way - storedProvenance, err := ecp.provenanceStore.GetProvenance(ctx, provenanceOrgAdapter{ - inner: &contactPoint, - orgID: orgID, + storedProvenance, err := ecp.provenanceStore.GetProvenance(ctx, ProvenanceOrgAdapter{ + Inner: &contactPoint, + OrgID: orgID, }) if err != nil { return err @@ -283,9 +283,9 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in if err != nil { return err } - adapter := provenanceOrgAdapter{ - inner: &contactPoint, - orgID: orgID, + adapter := ProvenanceOrgAdapter{ + Inner: &contactPoint, + OrgID: orgID, } err = ecp.provenanceStore.SetProvenance(ctx, adapter, provenance) if err != nil { @@ -330,11 +330,11 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in return err } return ecp.xact.InTransaction(ctx, func(ctx context.Context) error { - err := ecp.provenanceStore.DeleteProvenance(ctx, provenanceOrgAdapter{ - inner: &apimodels.EmbeddedContactPoint{ + err := ecp.provenanceStore.DeleteProvenance(ctx, ProvenanceOrgAdapter{ + Inner: &apimodels.EmbeddedContactPoint{ UID: uid, }, - orgID: orgID, + OrgID: orgID, }) if err != nil { return err diff --git a/pkg/services/ngalert/provisioning/contactpoints_test.go b/pkg/services/ngalert/provisioning/contactpoints_test.go index cb70fe05baa..d9c358aca33 100644 --- a/pkg/services/ngalert/provisioning/contactpoints_test.go +++ b/pkg/services/ngalert/provisioning/contactpoints_test.go @@ -178,7 +178,7 @@ func TestContactPointInUse(t *testing.T) { func createContactPointServiceSut(secretService secrets.Service) *ContactPointService { return &ContactPointService{ amStore: newFakeAMConfigStore(), - provenanceStore: newFakeProvisioningStore(), + provenanceStore: NewFakeProvisioningStore(), xact: newNopTransactionManager(), encryptionService: secretService, log: log.NewNopLogger(), diff --git a/pkg/services/ngalert/provisioning/notification_policies.go b/pkg/services/ngalert/provisioning/notification_policies.go index e77b5f11aea..87582b012f8 100644 --- a/pkg/services/ngalert/provisioning/notification_policies.go +++ b/pkg/services/ngalert/provisioning/notification_policies.go @@ -25,47 +25,39 @@ func NewNotificationPolicyService(am AMConfigStore, prov ProvisioningStore, xact } } -// TODO: move to Swagger codegen -type EmbeddedRoutingTree struct { - definitions.Route - Provenance models.Provenance -} - func (nps *NotificationPolicyService) GetAMConfigStore() AMConfigStore { return nps.amStore } -func (nps *NotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (EmbeddedRoutingTree, error) { +func (nps *NotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { q := models.GetLatestAlertmanagerConfigurationQuery{ OrgID: orgID, } err := nps.amStore.GetLatestAlertmanagerConfiguration(ctx, &q) if err != nil { - return EmbeddedRoutingTree{}, err + return definitions.Route{}, err } cfg, err := DeserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) if err != nil { - return EmbeddedRoutingTree{}, err + return definitions.Route{}, err } if cfg.AlertmanagerConfig.Config.Route == nil { - return EmbeddedRoutingTree{}, fmt.Errorf("no route present in current alertmanager config") + return definitions.Route{}, fmt.Errorf("no route present in current alertmanager config") } - adapter := provenanceOrgAdapter{ - inner: cfg.AlertmanagerConfig.Route, - orgID: orgID, + adapter := ProvenanceOrgAdapter{ + Inner: cfg.AlertmanagerConfig.Route, + OrgID: orgID, } provenance, err := nps.provenanceStore.GetProvenance(ctx, adapter) if err != nil { - return EmbeddedRoutingTree{}, err + return definitions.Route{}, err } - result := EmbeddedRoutingTree{ - Route: *cfg.AlertmanagerConfig.Route, - Provenance: provenance, - } + result := *cfg.AlertmanagerConfig.Route + result.Provenance = provenance return result, nil } @@ -103,9 +95,9 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI if err != nil { return err } - adapter := provenanceOrgAdapter{ - inner: &tree, - orgID: orgID, + adapter := ProvenanceOrgAdapter{ + Inner: &tree, + OrgID: orgID, } err = nps.provenanceStore.SetProvenance(ctx, adapter, p) if err != nil { @@ -119,20 +111,3 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI return nil } - -type provenanceOrgAdapter struct { - inner models.ProvisionableInOrg - orgID int64 -} - -func (a provenanceOrgAdapter) ResourceType() string { - return a.inner.ResourceType() -} - -func (a provenanceOrgAdapter) ResourceID() string { - return a.inner.ResourceID() -} - -func (a provenanceOrgAdapter) ResourceOrgID() int64 { - return a.orgID -} diff --git a/pkg/services/ngalert/provisioning/notification_policies_test.go b/pkg/services/ngalert/provisioning/notification_policies_test.go index 2a27b6b3554..2b4d4327bab 100644 --- a/pkg/services/ngalert/provisioning/notification_policies_test.go +++ b/pkg/services/ngalert/provisioning/notification_policies_test.go @@ -75,7 +75,7 @@ func TestNotificationPolicyService(t *testing.T) { func createNotificationPolicyServiceSut() *NotificationPolicyService { return &NotificationPolicyService{ amStore: newFakeAMConfigStore(), - provenanceStore: newFakeProvisioningStore(), + provenanceStore: NewFakeProvisioningStore(), xact: newNopTransactionManager(), log: log.NewNopLogger(), } diff --git a/pkg/services/ngalert/provisioning/persist.go b/pkg/services/ngalert/provisioning/persist.go index eb428055a86..0be6e093dd3 100644 --- a/pkg/services/ngalert/provisioning/persist.go +++ b/pkg/services/ngalert/provisioning/persist.go @@ -24,3 +24,20 @@ type ProvisioningStore interface { type TransactionManager interface { InTransaction(ctx context.Context, work func(ctx context.Context) error) error } + +type ProvenanceOrgAdapter struct { + Inner models.ProvisionableInOrg + OrgID int64 +} + +func (a ProvenanceOrgAdapter) ResourceType() string { + return a.Inner.ResourceType() +} + +func (a ProvenanceOrgAdapter) ResourceID() string { + return a.Inner.ResourceID() +} + +func (a ProvenanceOrgAdapter) ResourceOrgID() int64 { + return a.OrgID +} diff --git a/pkg/services/ngalert/provisioning/testing.go b/pkg/services/ngalert/provisioning/testing.go index aba31dfd513..4693460c1c8 100644 --- a/pkg/services/ngalert/provisioning/testing.go +++ b/pkg/services/ngalert/provisioning/testing.go @@ -92,7 +92,7 @@ type fakeProvisioningStore struct { records map[int64]map[string]models.Provenance } -func newFakeProvisioningStore() *fakeProvisioningStore { +func NewFakeProvisioningStore() *fakeProvisioningStore { return &fakeProvisioningStore{ records: map[int64]map[string]models.Provenance{}, } diff --git a/pkg/services/ngalert/schedule/compat.go b/pkg/services/ngalert/schedule/compat.go index fda9b1da948..eaea0f70aee 100644 --- a/pkg/services/ngalert/schedule/compat.go +++ b/pkg/services/ngalert/schedule/compat.go @@ -27,7 +27,7 @@ const ( // stateToPostableAlert converts a state to a model that is accepted by Alertmanager. Annotations and Labels are copied from the state. // - if state has at least one result, a new label '__value_string__' is added to the label set -// - the alert's GeneratorURL is constructed to point to the alert edit page +// - the alert's GeneratorURL is constructed to point to the alert detail view // - if evaluation state is either NoData or Error, the resulting set of labels is changed: // - original alert name (label: model.AlertNameLabel) is backed up to OriginalAlertName // - label model.AlertNameLabel is overwritten to either NoDataAlertName or ErrorAlertName @@ -42,7 +42,7 @@ func stateToPostableAlert(alertState *state.State, appURL *url.URL) *models.Post var urlStr string if uid := nL[ngModels.RuleUIDLabel]; len(uid) > 0 && appURL != nil { u := *appURL - u.Path = path.Join(u.Path, fmt.Sprintf("/alerting/%s/edit", uid)) + u.Path = path.Join(u.Path, fmt.Sprintf("/alerting/grafana/%s/view", uid)) urlStr = u.String() } else if appURL != nil { urlStr = appURL.String() diff --git a/pkg/services/ngalert/schedule/compat_test.go b/pkg/services/ngalert/schedule/compat_test.go index 6c67cc04b23..7eda47a8d47 100644 --- a/pkg/services/ngalert/schedule/compat_test.go +++ b/pkg/services/ngalert/schedule/compat_test.go @@ -60,7 +60,7 @@ func Test_stateToPostableAlert(t *testing.T) { alertState.Labels[ngModels.RuleUIDLabel] = alertState.AlertRuleUID result := stateToPostableAlert(alertState, appURL) u := *appURL - u.Path = u.Path + "/alerting/" + alertState.AlertRuleUID + "/edit" + u.Path = u.Path + "/alerting/grafana/" + alertState.AlertRuleUID + "/view" require.Equal(t, u.String(), result.Alert.GeneratorURL.String()) }) diff --git a/pkg/services/ngalert/schedule/schedule.go b/pkg/services/ngalert/schedule/schedule.go index a15438d5f8d..04ab92e7aff 100644 --- a/pkg/services/ngalert/schedule/schedule.go +++ b/pkg/services/ngalert/schedule/schedule.go @@ -31,8 +31,6 @@ type ScheduleService interface { // Run the scheduler until the context is canceled or the scheduler returns // an error. The scheduler is terminated when this function returns. Run(context.Context) error - Pause() error - Unpause() error // AlertmanagersFor returns all the discovered Alertmanager URLs for the // organization. @@ -123,7 +121,7 @@ type SchedulerCfg struct { // NewScheduler returns a new schedule. func NewScheduler(cfg SchedulerCfg, expressionService *expr.Service, appURL *url.URL, stateManager *state.Manager) *schedule { - ticker := alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds())) + ticker := alerting.NewTicker(cfg.C, cfg.BaseInterval, cfg.Metrics.Ticker) sch := schedule{ registry: alertRuleRegistry{alertRuleInfo: make(map[models.AlertRuleKey]*alertRuleInfo)}, @@ -154,24 +152,6 @@ func NewScheduler(cfg SchedulerCfg, expressionService *expr.Service, appURL *url return &sch } -func (sch *schedule) Pause() error { - if sch == nil { - return fmt.Errorf("scheduler is not initialised") - } - sch.ticker.Pause() - sch.log.Info("alert rule scheduler paused", "now", sch.clock.Now()) - return nil -} - -func (sch *schedule) Unpause() error { - if sch == nil { - return fmt.Errorf("scheduler is not initialised") - } - sch.ticker.Unpause() - sch.log.Info("alert rule scheduler unpaused", "now", sch.clock.Now()) - return nil -} - func (sch *schedule) Run(ctx context.Context) error { var wg sync.WaitGroup wg.Add(2) @@ -789,7 +769,7 @@ type evaluation struct { func (sch *schedule) overrideCfg(cfg SchedulerCfg) { sch.clock = cfg.C sch.baseInterval = cfg.BaseInterval - sch.ticker = alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds())) + sch.ticker = alerting.NewTicker(cfg.C, cfg.BaseInterval, cfg.Metrics.Ticker) sch.evalAppliedFunc = cfg.EvalAppliedFunc sch.stopAppliedFunc = cfg.StopAppliedFunc } diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 7f5f8da4cac..5882a49dc12 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/sender" "github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -1015,7 +1016,7 @@ func setupScheduler(t *testing.T, rs store.RuleStore, is store.InstanceStore, ac m := metrics.NewNGAlert(registry) secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue - moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, ¬ifier.FakeConfigStore{}, ¬ifier.FakeOrgStore{}, ¬ifier.FakeKVStore{}, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) + moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, ¬ifier.FakeConfigStore{}, ¬ifier.FakeOrgStore{}, ¬ifier.FakeKVStore{}, provisioning.NewFakeProvisioningStore(), decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) schedCfg := SchedulerCfg{ diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index a40f8fc0c82..5deb39d2555 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -76,7 +76,7 @@ func (st *Manager) Warm(ctx context.Context) { ruleCmd := ngModels.ListAlertRulesQuery{ OrgID: orgId, } - if err := st.ruleStore.GetOrgAlertRules(ctx, &ruleCmd); err != nil { + if err := st.ruleStore.ListAlertRules(ctx, &ruleCmd); err != nil { st.log.Error("unable to fetch previous state", "msg", err.Error()) } diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index ff1df1709ce..bc6a0f97c00 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -38,8 +38,9 @@ type RuleStore interface { DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) error GetAlertRulesForScheduling(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error - GetOrgAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error - GetAlertRules(ctx context.Context, query *ngmodels.GetAlertRulesQuery) error + ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error + // GetRuleGroups returns the unique rule groups across all organizations. + GetRuleGroups(ctx context.Context, query *ngmodels.ListRuleGroupsQuery) error GetUserVisibleNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error) GetNamespaceByTitle(context.Context, string, int64, *models.SignedInUser, bool) (*models.Folder, error) InsertAlertRules(ctx context.Context, rule []ngmodels.AlertRule) error @@ -226,33 +227,39 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, rules []UpdateRule) erro } // GetOrgAlertRules is a handler for retrieving alert rules of specific organisation. -func (st DBstore) GetOrgAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error { +func (st DBstore) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error { return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - alertRules := make([]*ngmodels.AlertRule, 0) - q := "SELECT * FROM alert_rule WHERE org_id = ?" - params := []interface{}{query.OrgID} + q := sess.Table("alert_rule") - if len(query.NamespaceUIDs) > 0 { - placeholders := make([]string, 0, len(query.NamespaceUIDs)) - for _, folderUID := range query.NamespaceUIDs { - params = append(params, folderUID) - placeholders = append(placeholders, "?") - } - q = fmt.Sprintf("%s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ",")) + if query.OrgID >= 0 { + q = q.Where("org_id = ?", query.OrgID) } if query.DashboardUID != "" { - params = append(params, query.DashboardUID) - q = fmt.Sprintf("%s AND dashboard_uid = ?", q) + q = q.Where("dashboard_uid = ?", query.DashboardUID) if query.PanelID != 0 { - params = append(params, query.PanelID) - q = fmt.Sprintf("%s AND panel_id = ?", q) + q = q.Where("panel_id = ?", query.PanelID) } } - q = fmt.Sprintf("%s ORDER BY id ASC", q) + if len(query.NamespaceUIDs) > 0 { + args := make([]interface{}, 0, len(query.NamespaceUIDs)) + in := make([]string, 0, len(query.NamespaceUIDs)) + for _, namespaceUID := range query.NamespaceUIDs { + args = append(args, namespaceUID) + in = append(in, "?") + } + q = q.Where(fmt.Sprintf("namespace_uid IN (%s)", strings.Join(in, ",")), args...) + } - if err := sess.SQL(q, params...).Find(&alertRules); err != nil { + if query.RuleGroup != "" { + q = q.Where("rule_group = ?", query.RuleGroup) + } + + q = q.OrderBy("id ASC") + + alertRules := make([]*ngmodels.AlertRule, 0) + if err := q.Find(&alertRules); err != nil { return err } @@ -261,26 +268,13 @@ func (st DBstore) GetOrgAlertRules(ctx context.Context, query *ngmodels.ListAler }) } -// GetAlertRules is a handler for retrieving rule group alert rules of specific organisation. -func (st DBstore) GetAlertRules(ctx context.Context, query *ngmodels.GetAlertRulesQuery) error { +func (st DBstore) GetRuleGroups(ctx context.Context, query *ngmodels.ListRuleGroupsQuery) error { return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - q := sess.Table("alert_rule").Where("org_id = ? AND namespace_uid = ?", query.OrgID, query.NamespaceUID) - if query.RuleGroup != nil { - q = q.Where("rule_group = ?", *query.RuleGroup) - } - if query.DashboardUID != "" { - q = q.Where("dashboard_uid = ?", query.DashboardUID) - if query.PanelID != 0 { - q = q.Where("panel_id = ?", query.PanelID) - } - } - - alertRules := make([]*ngmodels.AlertRule, 0) - if err := q.Find(&alertRules); err != nil { + ruleGroups := make([]string, 0) + if err := sess.Table("alert_rule").Distinct("rule_group").Find(&ruleGroups); err != nil { return err } - - query.Result = alertRules + query.Result = ruleGroups return nil }) } diff --git a/pkg/services/ngalert/store/testing.go b/pkg/services/ngalert/store/testing.go index 0c69fd5f522..1a3af60bc30 100644 --- a/pkg/services/ngalert/store/testing.go +++ b/pkg/services/ngalert/store/testing.go @@ -166,42 +166,80 @@ func (f *FakeRuleStore) GetAlertRulesForScheduling(_ context.Context, q *models. return nil } -func (f *FakeRuleStore) GetOrgAlertRules(_ context.Context, q *models.ListAlertRulesQuery) error { +func (f *FakeRuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQuery) error { f.mtx.Lock() defer f.mtx.Unlock() f.RecordedOps = append(f.RecordedOps, *q) - rules, ok := f.Rules[q.OrgID] - if !ok { - return nil - } - q.Result = rules - return nil -} -func (f *FakeRuleStore) GetAlertRules(_ context.Context, q *models.GetAlertRulesQuery) error { - f.mtx.Lock() - defer f.mtx.Unlock() - f.RecordedOps = append(f.RecordedOps, *q) if err := f.Hook(*q); err != nil { return err } - rules, ok := f.Rules[q.OrgID] - if !ok { - return nil + + hasDashboard := func(r *models.AlertRule, dashboardUID string, panelID int64) bool { + if dashboardUID != "" { + if r.DashboardUID == nil || *r.DashboardUID != dashboardUID { + return false + } + if panelID > 0 { + if r.PanelID == nil || *r.PanelID != panelID { + return false + } + } + } + return true } - var result []*models.AlertRule - for _, rule := range rules { - if q.NamespaceUID != rule.NamespaceUID { + + hasNamespace := func(r *models.AlertRule, namespaceUIDs []string) bool { + if len(namespaceUIDs) > 0 { + var ok bool + for _, uid := range q.NamespaceUIDs { + if uid == r.NamespaceUID { + ok = true + break + } + } + if !ok { + return false + } + } + return true + } + + for _, r := range f.Rules[q.OrgID] { + if !hasDashboard(r, q.DashboardUID, q.PanelID) { continue } - if q.RuleGroup != nil && *q.RuleGroup != rule.RuleGroup { + if !hasNamespace(r, q.NamespaceUIDs) { continue } - result = append(result, rule) + if q.RuleGroup != "" && r.RuleGroup != q.RuleGroup { + continue + } + q.Result = append(q.Result, r) } - q.Result = result + return nil } + +func (f *FakeRuleStore) GetRuleGroups(_ context.Context, q *models.ListRuleGroupsQuery) error { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, *q) + + m := make(map[string]struct{}) + for _, rules := range f.Rules { + for _, rule := range rules { + m[rule.RuleGroup] = struct{}{} + } + } + + for s := range m { + q.Result = append(q.Result, s) + } + + return nil +} + func (f *FakeRuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ *models2.SignedInUser) (map[string]*models2.Folder, error) { f.mtx.Lock() defer f.mtx.Unlock() @@ -218,6 +256,7 @@ func (f *FakeRuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, } return namespacesMap, nil } + func (f *FakeRuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *models2.SignedInUser, _ bool) (*models2.Folder, error) { folders := f.Folders[orgID] for _, folder := range folders { diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 19de8f046a2..69f79d427ae 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -113,12 +113,12 @@ func CreateTestAlertRuleWithLabels(t *testing.T, ctx context.Context, dbstore *s }) require.NoError(t, err) - q := models.GetAlertRulesQuery{ - OrgID: orgID, - NamespaceUID: "namespace", - RuleGroup: &ruleGroup, + q := models.ListAlertRulesQuery{ + OrgID: orgID, + NamespaceUIDs: []string{"namespace"}, + RuleGroup: ruleGroup, } - err = dbstore.GetAlertRules(ctx, &q) + err = dbstore.ListAlertRules(ctx, &q) require.NoError(t, err) require.NotEmpty(t, q.Result) diff --git a/pkg/services/preference/prefimpl/pref.go b/pkg/services/preference/prefimpl/pref.go index c43fb7d98c5..0acc2904c11 100644 --- a/pkg/services/preference/prefimpl/pref.go +++ b/pkg/services/preference/prefimpl/pref.go @@ -15,7 +15,7 @@ type Service struct { cfg *setting.Cfg } -func ProvideService(db db.DB, cfg *setting.Cfg) *Service { +func ProvideService(db db.DB, cfg *setting.Cfg) pref.Service { return &Service{ store: &sqlStore{ db: db, @@ -30,6 +30,7 @@ func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreference OrgID: query.OrgID, UserID: query.UserID, } + prefs, err := s.store.List(ctx, listQuery) if err != nil { return nil, err @@ -64,7 +65,10 @@ func (s *Service) Get(ctx context.Context, query *pref.GetPreferenceQuery) (*pre TeamID: query.TeamID, } prefs, err := s.store.Get(ctx, getPref) - if err != nil && !errors.Is(err, pref.ErrPrefNotFound) { + if errors.Is(err, pref.ErrPrefNotFound) { + return &pref.Preference{}, nil + } + if err != nil { return nil, err } return prefs, nil diff --git a/pkg/services/preference/prefimpl/pref_test.go b/pkg/services/preference/prefimpl/pref_test.go index b1e439b6830..2e6158ae56b 100644 --- a/pkg/services/preference/prefimpl/pref_test.go +++ b/pkg/services/preference/prefimpl/pref_test.go @@ -73,30 +73,42 @@ func TestPreferencesService(t *testing.T) { Navbar: team1NavbarPreferences, } + t.Run("Get should return nothing", func(t *testing.T) { + prefStoreFake.ExpectedGetError = pref.ErrPrefNotFound + preference, err := prefService.Get(context.Background(), &pref.GetPreferenceQuery{}) + require.NoError(t, err) + expected := &pref.Preference{} + if diff := cmp.Diff(expected, preference); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + prefStoreFake.ExpectedError = nil + }) + t.Run("GetDefaults should return defaults", func(t *testing.T) { prefService.cfg = setting.NewCfg() prefService.cfg.DefaultTheme = "light" prefService.cfg.DateFormats.DefaultTimezone = "UTC" - preferences := prefService.GetDefaults() + preference := prefService.GetDefaults() expected := &pref.Preference{ Theme: "light", Timezone: "UTC", HomeDashboardID: 0, JSONData: &pref.PreferenceJSONData{}, } - if diff := cmp.Diff(expected, preferences); diff != "" { + if diff := cmp.Diff(expected, preference); diff != "" { t.Fatalf("Result mismatch (-want +got):\n%s", diff) } }) t.Run("GetDefaults with no saved preferences should return defaults", func(t *testing.T) { + prefStoreFake.ExpectedError = nil prefStoreFake.ExpectedPreference = &pref.Preference{ Theme: "light", Timezone: "UTC", } - query := &pref.GetPreferenceWithDefaultsQuery{} - preferences, err := prefService.GetWithDefaults(context.Background(), query) + query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1} + preference, err := prefService.GetWithDefaults(context.Background(), query) require.NoError(t, err) expected := &pref.Preference{ Theme: "light", @@ -104,7 +116,7 @@ func TestPreferencesService(t *testing.T) { HomeDashboardID: 0, JSONData: &emptyPreferencesJsonData, } - if diff := cmp.Diff(expected, preferences); diff != "" { + if diff := cmp.Diff(expected, preference); diff != "" { t.Fatalf("Result mismatch (-want +got):\n%s", diff) } }) @@ -127,7 +139,7 @@ func TestPreferencesService(t *testing.T) { }, } query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 1} - preferences, err := prefService.GetWithDefaults(context.Background(), query) + preference, err := prefService.GetWithDefaults(context.Background(), query) require.NoError(t, err) expected := &pref.Preference{ Theme: "light", @@ -136,7 +148,7 @@ func TestPreferencesService(t *testing.T) { HomeDashboardID: 4, JSONData: &pref.PreferenceJSONData{}, } - if diff := cmp.Diff(expected, preferences); diff != "" { + if diff := cmp.Diff(expected, preference); diff != "" { t.Fatalf("Result mismatch (-want +got):\n%s", diff) } }) @@ -162,7 +174,7 @@ func TestPreferencesService(t *testing.T) { } prefService.GetDefaults().HomeDashboardID = 1 query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 2} - preferences, err := prefService.GetWithDefaults(context.Background(), query) + preference, err := prefService.GetWithDefaults(context.Background(), query) require.NoError(t, err) expected := &pref.Preference{ Theme: "light", @@ -171,7 +183,7 @@ func TestPreferencesService(t *testing.T) { HomeDashboardID: 4, JSONData: &pref.PreferenceJSONData{}, } - if diff := cmp.Diff(expected, preferences); diff != "" { + if diff := cmp.Diff(expected, preference); diff != "" { t.Fatalf("Result mismatch (-want +got):\n%s", diff) } }) diff --git a/pkg/services/preference/preftest/fake.go b/pkg/services/preference/preftest/fake.go index f52acea2047..8a2ca391c0f 100644 --- a/pkg/services/preference/preftest/fake.go +++ b/pkg/services/preference/preftest/fake.go @@ -30,3 +30,7 @@ func (f *FakePreferenceService) Save(ctx context.Context, cmd *pref.SavePreferen func (f *FakePreferenceService) GetDefaults() *pref.Preference { return f.ExpectedPreference } + +func (f *FakePreferenceService) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) error { + return f.ExpectedError +} diff --git a/pkg/services/serviceaccounts/manager/roles.go b/pkg/services/serviceaccounts/manager/roles.go index 7c323043a95..b0164c2319a 100644 --- a/pkg/services/serviceaccounts/manager/roles.go +++ b/pkg/services/serviceaccounts/manager/roles.go @@ -31,11 +31,7 @@ func RegisterRoles(ac accesscontrol.AccessControl) error { DisplayName: "Service accounts writer", Description: "Create, delete, read, or query service accounts.", Group: "Service accounts", - Permissions: []accesscontrol.Permission{ - { - Action: serviceaccounts.ActionRead, - Scope: serviceaccounts.ScopeAll, - }, + Permissions: accesscontrol.ConcatPermissions(saReader.Role.Permissions, []accesscontrol.Permission{ { Action: serviceaccounts.ActionWrite, Scope: serviceaccounts.ScopeAll, @@ -47,7 +43,7 @@ func RegisterRoles(ac accesscontrol.AccessControl) error { Action: serviceaccounts.ActionDelete, Scope: serviceaccounts.ScopeAll, }, - }, + }), }, Grants: []string{string(models.ROLE_ADMIN)}, } diff --git a/pkg/services/serviceaccounts/manager/service.go b/pkg/services/serviceaccounts/manager/service.go index 71aba8ae775..aebed6a1b5c 100644 --- a/pkg/services/serviceaccounts/manager/service.go +++ b/pkg/services/serviceaccounts/manager/service.go @@ -16,7 +16,7 @@ import ( ) var ( - ServiceAccountFeatureToggleNotFound = "FeatureToggle service-accounts not found, try adding it to your custom.ini" + ServiceAccountFeatureToggleNotFound = "FeatureToggle serviceAccounts not found, try adding it to your custom.ini" ) type ServiceAccountsService struct { diff --git a/pkg/services/serviceaccounts/manager/service_test.go b/pkg/services/serviceaccounts/manager/service_test.go index 57f986a3fe9..f338e1f4b39 100644 --- a/pkg/services/serviceaccounts/manager/service_test.go +++ b/pkg/services/serviceaccounts/manager/service_test.go @@ -15,7 +15,7 @@ func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) { t.Run("feature toggle present, should call store function", func(t *testing.T) { storeMock := &tests.ServiceAccountsStoreMock{Calls: tests.Calls{}} svc := ServiceAccountsService{ - features: featuremgmt.WithFeatures("service-accounts", true), + features: featuremgmt.WithFeatures("serviceAccounts", true), store: storeMock} err := svc.DeleteServiceAccount(context.Background(), 1, 1) require.NoError(t, err) @@ -25,7 +25,7 @@ func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) { t.Run("no feature toggle present, should not call store function", func(t *testing.T) { svcMock := &tests.ServiceAccountsStoreMock{Calls: tests.Calls{}} svc := ServiceAccountsService{ - features: featuremgmt.WithFeatures("service-accounts", false), + features: featuremgmt.WithFeatures("serviceAccounts", false), store: svcMock, log: log.New("serviceaccounts-manager-test"), } diff --git a/pkg/services/sqlstore/alert_notification.go b/pkg/services/sqlstore/alert_notification.go index c8c515e81ee..ee780c53a44 100644 --- a/pkg/services/sqlstore/alert_notification.go +++ b/pkg/services/sqlstore/alert_notification.go @@ -54,7 +54,7 @@ func (ss *SQLStore) DeleteAlertNotification(ctx context.Context, cmd *models.Del func (ss *SQLStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error { existingNotification := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid} - if err := getAlertNotificationWithUidInternal(ctx, existingNotification, newSession(ctx)); err != nil { + if err := getAlertNotificationWithUidInternal(ctx, existingNotification, ss.newSession(ctx)); err != nil { return err } @@ -75,7 +75,7 @@ func (ss *SQLStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *mod } func (ss *SQLStore) GetAlertNotifications(ctx context.Context, query *models.GetAlertNotificationsQuery) error { - return getAlertNotificationInternal(ctx, query, newSession(ctx)) + return getAlertNotificationInternal(ctx, query, ss.newSession(ctx)) } func (ss *SQLStore) GetAlertNotificationUidWithId(ctx context.Context, query *models.GetAlertNotificationUidQuery) error { @@ -86,7 +86,7 @@ func (ss *SQLStore) GetAlertNotificationUidWithId(ctx context.Context, query *mo return nil } - err := getAlertNotificationUidInternal(ctx, query, newSession(ctx)) + err := getAlertNotificationUidInternal(ctx, query, ss.newSession(ctx)) if err != nil { return err } @@ -101,7 +101,7 @@ func newAlertNotificationUidCacheKey(orgID, notificationId int64) string { } func (ss *SQLStore) GetAlertNotificationsWithUid(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery) error { - return getAlertNotificationWithUidInternal(ctx, query, newSession(ctx)) + return getAlertNotificationWithUidInternal(ctx, query, ss.newSession(ctx)) } func (ss *SQLStore) GetAllAlertNotifications(ctx context.Context, query *models.GetAllAlertNotificationsQuery) error { @@ -440,7 +440,7 @@ func (ss *SQLStore) UpdateAlertNotification(ctx context.Context, cmd *models.Upd func (ss *SQLStore) UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error { getAlertNotificationWithUidQuery := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid} - if err := getAlertNotificationWithUidInternal(ctx, getAlertNotificationWithUidQuery, newSession(ctx)); err != nil { + if err := getAlertNotificationWithUidInternal(ctx, getAlertNotificationWithUidQuery, ss.newSession(ctx)); err != nil { return err } diff --git a/pkg/services/sqlstore/annotation_test.go b/pkg/services/sqlstore/annotation_test.go index 6b66861d0cb..e5362d49c06 100644 --- a/pkg/services/sqlstore/annotation_test.go +++ b/pkg/services/sqlstore/annotation_test.go @@ -337,7 +337,7 @@ func TestAnnotations(t *testing.T) { }) } -func TestAnnotationListingWithFGAC(t *testing.T) { +func TestAnnotationListingWithRBAC(t *testing.T) { sql := sqlstore.InitTestDB(t) sql.Cfg.IsFeatureToggleEnabled = func(key string) bool { return key == featuremgmt.FlagAccesscontrol diff --git a/pkg/services/sqlstore/dashboard_snapshot.go b/pkg/services/sqlstore/dashboard_snapshot.go index 38584e26a4e..9773b795fa1 100644 --- a/pkg/services/sqlstore/dashboard_snapshot.go +++ b/pkg/services/sqlstore/dashboard_snapshot.go @@ -68,47 +68,51 @@ func (ss *SQLStore) DeleteDashboardSnapshot(ctx context.Context, cmd *models.Del }) } -func (ss *SQLStore) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery) error { - snapshot := models.DashboardSnapshot{Key: query.Key, DeleteKey: query.DeleteKey} - has, err := x.Get(&snapshot) +func (ss *SQLStore) GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error { + return ss.WithDbSession(ctx, func(dbSess *DBSession) error { + snapshot := models.DashboardSnapshot{Key: query.Key, DeleteKey: query.DeleteKey} + has, err := dbSess.Get(&snapshot) - if err != nil { - return err - } else if !has { - return models.ErrDashboardSnapshotNotFound - } + if err != nil { + return err + } else if !has { + return models.ErrDashboardSnapshotNotFound + } - query.Result = &snapshot - return nil + query.Result = &snapshot + return nil + }) } // SearchDashboardSnapshots returns a list of all snapshots for admins // for other roles, it returns snapshots created by the user -func (ss *SQLStore) SearchDashboardSnapshots(query *models.GetDashboardSnapshotsQuery) error { - var snapshots = make(models.DashboardSnapshotsList, 0) +func (ss *SQLStore) SearchDashboardSnapshots(ctx context.Context, query *models.GetDashboardSnapshotsQuery) error { + return ss.WithDbSession(ctx, func(dbSess *DBSession) error { + var snapshots = make(models.DashboardSnapshotsList, 0) - sess := x.NewSession() - if query.Limit > 0 { - sess.Limit(query.Limit) - } - sess.Table("dashboard_snapshot") + sess := ss.NewSession(ctx) + if query.Limit > 0 { + sess.Limit(query.Limit) + } + sess.Table("dashboard_snapshot") - if query.Name != "" { - sess.Where("name LIKE ?", query.Name) - } + if query.Name != "" { + sess.Where("name LIKE ?", query.Name) + } - // admins can see all snapshots, everyone else can only see their own snapshots - switch { - case query.SignedInUser.OrgRole == models.ROLE_ADMIN: - sess.Where("org_id = ?", query.OrgId) - case !query.SignedInUser.IsAnonymous: - sess.Where("org_id = ? AND user_id = ?", query.OrgId, query.SignedInUser.UserId) - default: + // admins can see all snapshots, everyone else can only see their own snapshots + switch { + case query.SignedInUser.OrgRole == models.ROLE_ADMIN: + sess.Where("org_id = ?", query.OrgId) + case !query.SignedInUser.IsAnonymous: + sess.Where("org_id = ? AND user_id = ?", query.OrgId, query.SignedInUser.UserId) + default: + query.Result = snapshots + return nil + } + + err := sess.Find(&snapshots) query.Result = snapshots - return nil - } - - err := sess.Find(&snapshots) - query.Result = snapshots - return err + return err + }) } diff --git a/pkg/services/sqlstore/dashboard_snapshot_test.go b/pkg/services/sqlstore/dashboard_snapshot_test.go index ebaf1091ebb..97127a9978a 100644 --- a/pkg/services/sqlstore/dashboard_snapshot_test.go +++ b/pkg/services/sqlstore/dashboard_snapshot_test.go @@ -47,7 +47,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { t.Run("Should be able to get snapshot by key", func(t *testing.T) { query := models.GetDashboardSnapshotQuery{Key: "hej"} - err := sqlstore.GetDashboardSnapshot(&query) + err := sqlstore.GetDashboardSnapshot(context.Background(), &query) require.NoError(t, err) assert.NotNil(t, query.Result) @@ -69,7 +69,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { OrgId: 1, SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}, } - err := sqlstore.SearchDashboardSnapshots(&query) + err := sqlstore.SearchDashboardSnapshots(context.Background(), &query) require.NoError(t, err) t.Run("Should return all the snapshots", func(t *testing.T) { @@ -83,7 +83,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { OrgId: 1, SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_EDITOR, UserId: 1000}, } - err := sqlstore.SearchDashboardSnapshots(&query) + err := sqlstore.SearchDashboardSnapshots(context.Background(), &query) require.NoError(t, err) t.Run("Should return all the snapshots", func(t *testing.T) { @@ -97,7 +97,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { OrgId: 1, SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_EDITOR, UserId: 2}, } - err := sqlstore.SearchDashboardSnapshots(&query) + err := sqlstore.SearchDashboardSnapshots(context.Background(), &query) require.NoError(t, err) t.Run("Should not return any snapshots", func(t *testing.T) { @@ -124,7 +124,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { OrgId: 1, SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_EDITOR, IsAnonymous: true, UserId: 0}, } - err := sqlstore.SearchDashboardSnapshots(&query) + err := sqlstore.SearchDashboardSnapshots(context.Background(), &query) require.NoError(t, err) require.NotNil(t, query.Result) @@ -161,7 +161,7 @@ func TestDeleteExpiredSnapshots(t *testing.T) { OrgId: 1, SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}, } - err = sqlstore.SearchDashboardSnapshots(&query) + err = sqlstore.SearchDashboardSnapshots(context.Background(), &query) require.NoError(t, err) assert.Len(t, query.Result, 1) @@ -174,7 +174,7 @@ func TestDeleteExpiredSnapshots(t *testing.T) { OrgId: 1, SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}, } - err = sqlstore.SearchDashboardSnapshots(&query) + err = sqlstore.SearchDashboardSnapshots(context.Background(), &query) require.NoError(t, err) require.Len(t, query.Result, 1) diff --git a/pkg/services/sqlstore/migrations/accesscontrol/team_membership.go b/pkg/services/sqlstore/migrations/accesscontrol/team_membership.go index cdf576fcbc4..4197b9f5abd 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/team_membership.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/team_membership.go @@ -61,8 +61,8 @@ func (p *teamPermissionMigrator) setRolePermissions(roleID int64, permissions [] return nil } -// mapPermissionToFGAC translates the legacy membership (Member or Admin) into FGAC permissions -func (p *teamPermissionMigrator) mapPermissionToFGAC(permission models.PermissionType, teamID int64) []accesscontrol.Permission { +// mapPermissionToRBAC translates the legacy membership (Member or Admin) into RBAC permissions +func (p *teamPermissionMigrator) mapPermissionToRBAC(permission models.PermissionType, teamID int64) []accesscontrol.Permission { teamIDScope := accesscontrol.Scope("teams", "id", strconv.FormatInt(teamID, 10)) switch permission { case 0: @@ -222,7 +222,7 @@ func (p *teamPermissionMigrator) generateAssociatedPermissions(teamMemberships [ if !initialized { userPermissions = map[int64][]accesscontrol.Permission{} } - userPermissions[m.UserId] = append(userPermissions[m.UserId], p.mapPermissionToFGAC(m.Permission, m.TeamId)...) + userPermissions[m.UserId] = append(userPermissions[m.UserId], p.mapPermissionToRBAC(m.Permission, m.TeamId)...) userPermissionsByOrg[m.OrgId] = userPermissions } diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index 19f63024ff0..8bc1cd056ec 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -91,7 +91,7 @@ func TestMigrations(t *testing.T) { setupTeams(t, x) // Create managed user roles with teams permissions (ex: teams:read and teams.permissions:read) - setupUnecessaryFGACPermissions(t, x) + setupUnecessaryRBACPermissions(t, x) team1Scope := accesscontrol.Scope("teams", "id", "1") team2Scope := accesscontrol.Scope("teams", "id", "2") @@ -347,7 +347,7 @@ func setupTeams(t *testing.T, x *xorm.Engine) { require.Equal(t, int64(5), membersCount, "needed 5 members for this test to run") } -func setupUnecessaryFGACPermissions(t *testing.T, x *xorm.Engine) { +func setupUnecessaryRBACPermissions(t *testing.T, x *xorm.Engine) { t.Helper() now := time.Now() diff --git a/pkg/services/sqlstore/migrations/ualert/migration_test.go b/pkg/services/sqlstore/migrations/ualert/migration_test.go new file mode 100644 index 00000000000..8f7de0ef342 --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/migration_test.go @@ -0,0 +1,599 @@ +package ualert_test + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/prometheus/alertmanager/pkg/labels" + "github.com/stretchr/testify/require" + "xorm.io/xorm" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations/ualert" + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" + "github.com/grafana/grafana/pkg/setting" +) + +// TestAddDashAlertMigration tests the AddDashAlertMigration wrapper method that decides when to run the migration based on migration status and settings. +func TestAddDashAlertMigration(t *testing.T) { + x := setupTestDB(t) + + tc := []struct { + name string + config *setting.Cfg + isMigrationRun bool + expected []string // set of migration titles + }{ + { + name: "when unified alerting enabled and migration not already run, then add main migration and clear rmMigration log entry", + config: &setting.Cfg{ + UnifiedAlerting: setting.UnifiedAlertingSettings{ + Enabled: boolPointer(true), + }, + }, + isMigrationRun: false, + expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.RmMigTitle), ualert.MigTitle}, + }, + { + name: "when unified alerting disabled and migration is already run, then add rmMigration and clear main migration log entry", + config: &setting.Cfg{ + UnifiedAlerting: setting.UnifiedAlertingSettings{ + Enabled: boolPointer(false), + }, + }, + isMigrationRun: true, + expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.MigTitle), ualert.RmMigTitle}, + }, + { + name: "when unified alerting enabled and migration is already run, then do nothing", + config: &setting.Cfg{ + UnifiedAlerting: setting.UnifiedAlertingSettings{ + Enabled: boolPointer(true), + }, + }, + isMigrationRun: true, + expected: []string{}, + }, + { + name: "when unified alerting disabled and migration is not already run, then do nothing", + config: &setting.Cfg{ + UnifiedAlerting: setting.UnifiedAlertingSettings{ + Enabled: boolPointer(false), + }, + }, + isMigrationRun: false, + expected: []string{}, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + if tt.isMigrationRun { + log := migrator.MigrationLog{ + MigrationID: ualert.MigTitle, + SQL: "", + Timestamp: time.Now(), + Success: true, + } + _, err := x.Insert(log) + require.NoError(t, err) + } else { + _, err := x.Exec("DELETE FROM migration_log WHERE migration_id = ?", ualert.MigTitle) + require.NoError(t, err) + } + + mg := migrator.NewMigrator(x, tt.config) + ualert.AddDashAlertMigration(mg) + require.Equal(t, tt.expected, mg.GetMigrationIDs(false)) + }) + } +} + +// TestDashAlertMigration tests the execution of the main DashAlertMigration. +func TestDashAlertMigration(t *testing.T) { + // Run initial migration to have a working DB. + x := setupTestDB(t) + + emailSettings := `{"addresses": "test"}` + slackSettings := `{"recipient": "test", "token": "test"}` + opsgenieSettings := `{"apiKey": "test"}` + + tc := []struct { + name string + legacyChannels []*models.AlertNotification + alerts []*models.Alert + + expected map[int64]*ualert.PostableUserConfig + expErr error + }{ + { + name: "general multi-org, multi-alert, multi-channel migration", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false), + createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, false), + createAlertNotification(t, int64(1), "notifier3", "opsgenie", opsgenieSettings, false), + createAlertNotification(t, int64(2), "notifier4", "email", emailSettings, false), + createAlertNotification(t, int64(2), "notifier5", "slack", slackSettings, false), + createAlertNotification(t, int64(2), "notifier6", "opsgenie", opsgenieSettings, true), // default + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}), + createAlert(t, int64(1), int64(1), int64(2), "alert2", []string{"notifier2", "notifier3"}), + createAlert(t, int64(1), int64(2), int64(3), "alert3", []string{"notifier3"}), + createAlert(t, int64(2), int64(3), int64(1), "alert4", []string{"notifier4"}), + createAlert(t, int64(2), int64(3), int64(2), "alert5", []string{"notifier4", "notifier5", "notifier6"}), + createAlert(t, int64(2), int64(4), int64(3), "alert6", []string{}), + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + Routes: []*ualert.Route{ + {Receiver: "autogen-contact-point-1", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert1")}, // These Matchers are temporary and will be replaced below with generated rule_uid. + {Receiver: "autogen-contact-point-2", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert2")}, + {Receiver: "autogen-contact-point-3", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert3")}, + }, + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}}, // email + {Name: "autogen-contact-point-2", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier2", Type: "slack"}, {Name: "notifier3", Type: "opsgenie"}}}, // slack+opsgenie + {Name: "autogen-contact-point-3", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier3", Type: "opsgenie"}}}, // opsgenie + {Name: "autogen-contact-point-default"}, // empty default + }, + }, + }, + int64(2): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + Routes: []*ualert.Route{ + {Receiver: "autogen-contact-point-4", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert4")}, + {Receiver: "autogen-contact-point-5", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert5")}, + }, + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-4", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier4", Type: "email"}, {Name: "notifier6", Type: "opsgenie"}}}, // email + {Name: "autogen-contact-point-5", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier4", Type: "email"}, {Name: "notifier5", Type: "slack"}, {Name: "notifier6", Type: "opsgenie"}}}, // email+slack+opsgenie + {Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier6", Type: "opsgenie"}}}, // empty default + }, + }, + }, + }, + }, + { + name: "when default channel, add to autogen-contact-point-default", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true), // default + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}), + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}}, + }, + }, + }, + }, + }, + { + name: "when default channels exist alongside non-default, add only defaults to autogen-contact-point-default", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true), // default + createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, false), + createAlertNotification(t, int64(1), "notifier3", "opsgenie", opsgenieSettings, true), // default + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier2"}), // + notifier1, notifier3 + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + Routes: []*ualert.Route{ + {Receiver: "autogen-contact-point-1", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert1")}, + }, + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier2", Type: "slack"}, {Name: "notifier3", Type: "opsgenie"}}}, + {Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier3", Type: "opsgenie"}}}, + }, + }, + }, + }, + }, + { + name: "when alert has only defaults, don't create route for it", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true), // default + createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, true), // default + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}), + createAlert(t, int64(1), int64(2), int64(3), "alert2", []string{}), + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier2", Type: "slack"}}}, + }, + }, + }, + }, + }, + { + name: "when alerts share all channels, only create one receiver for all of them", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false), + createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, false), + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1", "notifier2"}), + createAlert(t, int64(1), int64(1), int64(1), "alert2", []string{"notifier1", "notifier2"}), + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + Routes: []*ualert.Route{ + {Receiver: "autogen-contact-point-1", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert1")}, + {Receiver: "autogen-contact-point-1", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert2")}, + }, + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier2", Type: "slack"}}}, + {Name: "autogen-contact-point-default"}, + }, + }, + }, + }, + }, + { + name: "when channel not linked to any alerts, migrate it to autogen-unlinked-channel-recv", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true), // default + createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, true), // default + createAlertNotification(t, int64(1), "notifier3", "opsgenie", opsgenieSettings, false), // unlinked + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}), + createAlert(t, int64(1), int64(2), int64(3), "alert3", []string{}), + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier2", Type: "slack"}}}, + {Name: "autogen-unlinked-channel-recv", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier3", Type: "opsgenie"}}}, + }, + }, + }, + }, + }, + { + name: "when unsupported channels, do not migrate them", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "hipchat", "", false), + createAlertNotification(t, int64(1), "notifier2", "sensu", "", false), + }, + alerts: []*models.Alert{}, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-default"}, + }, + }, + }, + }, + }, + { + name: "when unsupported channel linked to alert, do not migrate only that channel", + legacyChannels: []*models.AlertNotification{ + createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false), + createAlertNotification(t, int64(1), "notifier2", "sensu", "", false), + }, + alerts: []*models.Alert{ + createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1", "notifier2"}), + }, + expected: map[int64]*ualert.PostableUserConfig{ + int64(1): { + AlertmanagerConfig: ualert.PostableApiAlertingConfig{ + Route: &ualert.Route{ + Receiver: "autogen-contact-point-default", + Routes: []*ualert.Route{ + {Receiver: "autogen-contact-point-1", Matchers: newMatchers(labels.MatchEqual, "alert_name", "alert1")}, + }, + }, + Receivers: []*ualert.PostableApiReceiver{ + {Name: "autogen-contact-point-1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}}, // no sensu + {Name: "autogen-contact-point-default"}, + }, + }, + }, + }, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + defer teardown(t, x) + setupLegacyAlertsTables(t, x, tt.legacyChannels, tt.alerts) + + _, errDeleteMig := x.Exec("DELETE FROM migration_log WHERE migration_id = ?", ualert.MigTitle) + require.NoError(t, errDeleteMig) + + alertMigrator := migrator.NewMigrator(x, &setting.Cfg{}) + alertMigrator.AddMigration(ualert.RmMigTitle, &ualert.RmMigration{}) + ualert.AddDashAlertMigration(alertMigrator) + + errRunningMig := alertMigrator.Start(false, 0) + require.NoError(t, errRunningMig) + + for orgId := range tt.expected { + amConfig := getAlertmanagerConfig(t, x, orgId) + + // Order of nested GrafanaManagedReceivers is not guaranteed. + cOpt := []cmp.Option{ + cmpopts.IgnoreFields(ualert.PostableGrafanaReceiver{}, "UID", "Settings", "SecureSettings"), + cmpopts.SortSlices(func(a, b *ualert.PostableGrafanaReceiver) bool { return a.Name < b.Name }), + cmpopts.SortSlices(func(a, b *ualert.PostableApiReceiver) bool { return a.Name < b.Name }), + } + if !cmp.Equal(tt.expected[orgId].AlertmanagerConfig.Receivers, amConfig.AlertmanagerConfig.Receivers, cOpt...) { + t.Errorf("Unexpected Receivers: %v", cmp.Diff(tt.expected[orgId].AlertmanagerConfig.Receivers, amConfig.AlertmanagerConfig.Receivers, cOpt...)) + } + + // Since routes and alerts are connecting solely by the Matchers on rule_uid, which is created at runtime we need to do some prep-work to populate the expected Matchers. + alertUids := getAlertNameToUidMap(t, x, orgId) + replaceAlertNameMatcherWithRuleUid(t, tt.expected[orgId].AlertmanagerConfig.Route.Routes, alertUids) + + // Order of nested routes is not guaranteed. + cOpt = []cmp.Option{ + cmpopts.SortSlices(func(a, b *ualert.Route) bool { return a.Receiver < b.Receiver }), + cmpopts.IgnoreUnexported(ualert.Route{}, labels.Matcher{}), + } + if !cmp.Equal(tt.expected[orgId].AlertmanagerConfig.Route, amConfig.AlertmanagerConfig.Route, cOpt...) { + t.Errorf("Unexpected Route: %v", cmp.Diff(tt.expected[orgId].AlertmanagerConfig.Route, amConfig.AlertmanagerConfig.Route, cOpt...)) + } + } + }) + } +} + +// setupTestDB prepares the sqlite database and runs OSS migrations to initialize the schemas. +func setupTestDB(t *testing.T) *xorm.Engine { + t.Helper() + testDB := sqlutil.SQLite3TestDB() + + x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr) + require.NoError(t, err) + + err = migrator.NewDialect(x).CleanDB() + require.NoError(t, err) + + mg := migrator.NewMigrator(x, &setting.Cfg{}) + migrations := &migrations.OSSMigrations{} + migrations.AddMigration(mg) + + err = mg.Start(false, 0) + require.NoError(t, err) + + return x +} + +var ( + now = time.Now() +) + +// createAlertNotification creates a legacy alert notification channel for inserting into the test database. +func createAlertNotification(t *testing.T, orgId int64, uid string, channelType string, settings string, defaultChannel bool) *models.AlertNotification { + t.Helper() + settingsJson := simplejson.New() + if settings != "" { + s, err := simplejson.NewJson([]byte(settings)) + if err != nil { + t.Fatalf("Failed to unmarshal alert notification json: %v", err) + } + settingsJson = s + } + + return &models.AlertNotification{ + OrgId: orgId, + Uid: uid, + Name: uid, // Same as uid to make testing easier. + Type: channelType, + DisableResolveMessage: false, + IsDefault: defaultChannel, + Settings: settingsJson, + SecureSettings: make(map[string][]byte), + Created: now, + Updated: now, + } +} + +// createAlert creates a legacy alert rule for inserting into the test database. +func createAlert(t *testing.T, orgId int64, dashboardId int64, panelsId int64, name string, notifierUids []string) *models.Alert { + t.Helper() + + var settings = simplejson.New() + if len(notifierUids) != 0 { + notifiers := make([]interface{}, 0) + for _, n := range notifierUids { + notifiers = append(notifiers, struct { + Uid string + }{Uid: n}) + } + + settings.Set("notifications", notifiers) + } + + return &models.Alert{ + OrgId: orgId, + DashboardId: dashboardId, + PanelId: panelsId, + Name: name, + Message: "message", + Frequency: int64(60), + For: time.Duration(time.Duration(60).Seconds()), + State: models.AlertStateOK, + Settings: settings, + NewStateDate: now, + Created: now, + Updated: now, + } +} + +// createDashboard creates a dashboard for inserting into the test database. +func createDashboard(t *testing.T, id int64, orgId int64, uid string) *models.Dashboard { + t.Helper() + return &models.Dashboard{ + Id: id, + OrgId: orgId, + Uid: uid, + Created: now, + Updated: now, + Title: uid, // Not tested, needed to satisfy contraint. + } +} + +// createDatasource creates a ddatasource for inserting into the test database. +func createDatasource(t *testing.T, id int64, orgId int64, uid string) *models.DataSource { + t.Helper() + return &models.DataSource{ + Id: id, + OrgId: orgId, + Uid: uid, + Created: now, + Updated: now, + Name: uid, // Not tested, needed to satisfy contraint. + } +} + +// teardown cleans the input tables between test cases. +func teardown(t *testing.T, x *xorm.Engine) { + _, err := x.Exec("DELETE from alert") + require.NoError(t, err) + _, err = x.Exec("DELETE from alert_notification") + require.NoError(t, err) + _, err = x.Exec("DELETE from dashboard") + require.NoError(t, err) + _, err = x.Exec("DELETE from data_source") + require.NoError(t, err) +} + +// setupLegacyAlertsTables inserts data into the legacy alerting tables that is needed for testing the migration. +func setupLegacyAlertsTables(t *testing.T, x *xorm.Engine, legacyChannels []*models.AlertNotification, alerts []*models.Alert) { + t.Helper() + + // Setup dashboards. + dashboards := []models.Dashboard{ + *createDashboard(t, 1, 1, "dash1-1"), + *createDashboard(t, 2, 1, "dash2-1"), + *createDashboard(t, 3, 2, "dash3-2"), + *createDashboard(t, 4, 2, "dash4-2"), + } + _, errDashboards := x.Insert(dashboards) + require.NoError(t, errDashboards) + + // Setup data_sources. + dataSources := []models.DataSource{ + *createDatasource(t, 1, 1, "ds1-1"), + *createDatasource(t, 2, 1, "ds2-1"), + *createDatasource(t, 3, 2, "ds3-2"), + *createDatasource(t, 4, 2, "ds4-2"), + } + _, errDataSourcess := x.Insert(dataSources) + require.NoError(t, errDataSourcess) + + if len(legacyChannels) > 0 { + _, channelErr := x.Insert(legacyChannels) + require.NoError(t, channelErr) + } + + if len(alerts) > 0 { + _, alertErr := x.Insert(alerts) + require.NoError(t, alertErr) + } +} + +// getAlertmanagerConfig retreives the Alertmanager Config from the database for a given orgId. +func getAlertmanagerConfig(t *testing.T, x *xorm.Engine, orgId int64) *ualert.PostableUserConfig { + amConfig := "" + _, err := x.Table("alert_configuration").Where("org_id = ?", orgId).Cols("alertmanager_configuration").Get(&amConfig) + require.NoError(t, err) + + config := ualert.PostableUserConfig{} + err = json.Unmarshal([]byte(amConfig), &config) + require.NoError(t, err) + return &config +} + +// getAlertNameToUidMap fetches alert_rules from database to create map of alert.Name -> alert.Uid. This is needed as alert Uid is created during migration and is used to match routes to alerts. +func getAlertNameToUidMap(t *testing.T, x *xorm.Engine, orgId int64) map[string]string { + t.Helper() + alerts := []struct { + Title string + Uid string + }{} + err := x.Table("alert_rule").Where("org_id = ?", orgId).Find(&alerts) + require.NoError(t, err) + + res := make(map[string]string) + for _, alert := range alerts { + res[alert.Title] = alert.Uid + } + return res +} + +// replaceAlertNameMatcherWithRuleUid replaces the stub matchers based on alert_name with the rule_uid's generated during migration. +func replaceAlertNameMatcherWithRuleUid(t *testing.T, rts []*ualert.Route, alertUids map[string]string) { + for _, rt := range rts { + if len(rt.Matchers) > 0 { + // Replace alert name matcher with generated rule_uid matcher + for _, m := range rt.Matchers { + if m.Name == "alert_name" { + m.Name = "rule_uid" + m.Value = alertUids[m.Value] + } + } + } + + // Recurse for nested routes. + replaceAlertNameMatcherWithRuleUid(t, rt.Routes, alertUids) + } +} + +func boolPointer(b bool) *bool { + return &b +} + +// newMatchers creates a new ualert.Matchers given MatchType, name, and value. +func newMatchers(t labels.MatchType, n, v string) ualert.Matchers { + matcher, _ := labels.NewMatcher(t, n, v) + return ualert.Matchers(labels.Matchers{matcher}) +} diff --git a/pkg/services/sqlstore/migrations/ualert/ualert.go b/pkg/services/sqlstore/migrations/ualert/ualert.go index 094f2222cde..8078478d0dc 100644 --- a/pkg/services/sqlstore/migrations/ualert/ualert.go +++ b/pkg/services/sqlstore/migrations/ualert/ualert.go @@ -10,15 +10,14 @@ import ( "strconv" "strings" - "github.com/grafana/grafana/pkg/setting" + pb "github.com/prometheus/alertmanager/silence/silencepb" + "xorm.io/xorm" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" - - pb "github.com/prometheus/alertmanager/silence/silencepb" - "xorm.io/xorm" ) const GENERAL_FOLDER = "General Alerting" diff --git a/pkg/services/sqlstore/migrations/ualert/ualert_test.go b/pkg/services/sqlstore/migrations/ualert/ualert_test.go index b136e3c2f3a..e5bdda6fc07 100644 --- a/pkg/services/sqlstore/migrations/ualert/ualert_test.go +++ b/pkg/services/sqlstore/migrations/ualert/ualert_test.go @@ -1,7 +1,9 @@ package ualert import ( + "encoding/json" "fmt" + "sort" "testing" "xorm.io/xorm" @@ -11,10 +13,33 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" + "github.com/prometheus/alertmanager/pkg/labels" "github.com/stretchr/testify/require" ) +var MigTitle = migTitle +var RmMigTitle = rmMigTitle +var ClearMigrationEntryTitle = clearMigrationEntryTitle + +type RmMigration = rmMigration + +func (m *Matchers) UnmarshalJSON(data []byte) error { + var lines []string + if err := json.Unmarshal(data, &lines); err != nil { + return err + } + for _, line := range lines { + pm, err := labels.ParseMatchers(line) + if err != nil { + return err + } + *m = append(*m, pm...) + } + sort.Sort(labels.Matchers(*m)) + return nil +} + func Test_validateAlertmanagerConfig(t *testing.T) { tc := []struct { name string diff --git a/pkg/services/sqlstore/mockstore/mockstore.go b/pkg/services/sqlstore/mockstore/mockstore.go index f66b16b723e..f8b7deac7bf 100644 --- a/pkg/services/sqlstore/mockstore/mockstore.go +++ b/pkg/services/sqlstore/mockstore/mockstore.go @@ -88,7 +88,7 @@ func (m *SQLStoreMock) DeleteDashboardSnapshot(ctx context.Context, cmd *models. return m.ExpectedError } -func (m *SQLStoreMock) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery) error { +func (m *SQLStoreMock) GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error { query.Result = m.ExpectedDashboardSnapshot return m.ExpectedError } @@ -97,7 +97,7 @@ func (m *SQLStoreMock) HasEditPermissionInFolders(ctx context.Context, query *mo return m.ExpectedError } -func (m *SQLStoreMock) SearchDashboardSnapshots(query *models.GetDashboardSnapshotsQuery) error { +func (m *SQLStoreMock) SearchDashboardSnapshots(ctx context.Context, query *models.GetDashboardSnapshotsQuery) error { return m.ExpectedError } @@ -289,23 +289,6 @@ func (m *SQLStoreMock) WithDbSession(ctx context.Context, callback sqlstore.DBTr return m.ExpectedError } -func (m *SQLStoreMock) GetPreferencesWithDefaults(ctx context.Context, query *models.GetPreferencesWithDefaultsQuery) error { - query.Result = &models.Preferences{} - return m.ExpectedError -} - -func (m *SQLStoreMock) GetPreferences(ctx context.Context, query *models.GetPreferencesQuery) error { - return m.ExpectedError -} - -func (m *SQLStoreMock) SavePreferences(ctx context.Context, cmd *models.SavePreferencesCommand) error { - return m.ExpectedError -} - -func (m *SQLStoreMock) PatchPreferences(ctx context.Context, cmd *models.PatchPreferencesCommand) error { - return m.ExpectedError -} - func (m *SQLStoreMock) GetPluginSettings(ctx context.Context, orgID int64) ([]*models.PluginSetting, error) { return nil, m.ExpectedError } @@ -672,3 +655,7 @@ func (m *SQLStoreMock) GetDashboardPermissionsForUser(ctx context.Context, query func (m *SQLStoreMock) GetDashboardSlugById(ctx context.Context, query *models.GetDashboardSlugByIdQuery) error { return m.ExpectedError } + +func (m *SQLStoreMock) IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error { + return m.ExpectedError +} diff --git a/pkg/services/sqlstore/org_users.go b/pkg/services/sqlstore/org_users.go index 60d7e5ca8f7..a80bdad0e6b 100644 --- a/pkg/services/sqlstore/org_users.go +++ b/pkg/services/sqlstore/org_users.go @@ -107,8 +107,8 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu whereParams = append(whereParams, query.UserID) } - whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = ?", dialect.Quote("user"))) - whereParams = append(whereParams, dialect.BooleanStr(false)) + whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = ?", ss.Dialect.Quote("user"))) + whereParams = append(whereParams, ss.Dialect.BooleanStr(false)) if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) && query.User != nil { acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users:id:", accesscontrol.ActionOrgUsersRead) @@ -121,7 +121,7 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu if query.Query != "" { queryWithWildcards := "%" + query.Query + "%" - whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)") + whereConditions = append(whereConditions, "(email "+ss.Dialect.LikeStr()+" ? OR name "+ss.Dialect.LikeStr()+" ? OR login "+ss.Dialect.LikeStr()+" ?)") whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards) } @@ -186,7 +186,7 @@ func (ss *SQLStore) SearchOrgUsers(ctx context.Context, query *models.SearchOrgU if query.Query != "" { queryWithWildcards := "%" + query.Query + "%" - whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)") + whereConditions = append(whereConditions, "(email "+ss.Dialect.LikeStr()+" ? OR name "+ss.Dialect.LikeStr()+" ? OR login "+ss.Dialect.LikeStr()+" ?)") whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards) } @@ -260,7 +260,7 @@ func (ss *SQLStore) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUser } } - // validate that after delete there is at least one user with admin role in org + // validate that after delete, there is at least one user with admin role in org if err := validateOneAdminLeftInOrg(cmd.OrgId, sess); err != nil { return err } diff --git a/pkg/services/sqlstore/preferences.go b/pkg/services/sqlstore/preferences.go deleted file mode 100644 index 6d9a7fe84ef..00000000000 --- a/pkg/services/sqlstore/preferences.go +++ /dev/null @@ -1,212 +0,0 @@ -package sqlstore - -import ( - "context" - "strings" - "time" - - "github.com/grafana/grafana/pkg/models" -) - -func (ss *SQLStore) GetPreferencesWithDefaults(ctx context.Context, query *models.GetPreferencesWithDefaultsQuery) error { - return ss.WithDbSession(ctx, func(dbSession *DBSession) error { - params := make([]interface{}, 0) - filter := "" - - if len(query.User.Teams) > 0 { - filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.User.Teams)-1) + ")) OR " - params = append(params, query.User.OrgId) - for _, v := range query.User.Teams { - params = append(params, v) - } - } - - filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)" - params = append(params, query.User.OrgId) - params = append(params, query.User.UserId) - params = append(params, query.User.OrgId) - prefs := make([]*models.Preferences, 0) - err := dbSession.Where(filter, params...). - OrderBy("user_id ASC, team_id ASC"). - Find(&prefs) - - if err != nil { - return err - } - - res := &models.Preferences{ - Theme: ss.Cfg.DefaultTheme, - Timezone: ss.Cfg.DateFormats.DefaultTimezone, - WeekStart: ss.Cfg.DateFormats.DefaultWeekStart, - HomeDashboardId: 0, - JsonData: &models.PreferencesJsonData{}, - } - - for _, p := range prefs { - if p.Theme != "" { - res.Theme = p.Theme - } - if p.Timezone != "" { - res.Timezone = p.Timezone - } - if p.WeekStart != "" { - res.WeekStart = p.WeekStart - } - if p.HomeDashboardId != 0 { - res.HomeDashboardId = p.HomeDashboardId - } - if p.JsonData != nil { - res.JsonData = p.JsonData - } - } - - query.Result = res - return nil - }) -} - -func (ss *SQLStore) GetPreferences(ctx context.Context, query *models.GetPreferencesQuery) error { - return ss.WithDbSession(ctx, func(sess *DBSession) error { - var prefs models.Preferences - exists, err := sess.Where("org_id=? AND user_id=? AND team_id=?", query.OrgId, query.UserId, query.TeamId).Get(&prefs) - - if err != nil { - return err - } - - if exists { - query.Result = &prefs - } else { - query.Result = new(models.Preferences) - } - - return nil - }) -} - -func (ss *SQLStore) SavePreferences(ctx context.Context, cmd *models.SavePreferencesCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - var prefs models.Preferences - exists, err := sess.Where("org_id=? AND user_id=? AND team_id=?", cmd.OrgId, cmd.UserId, cmd.TeamId).Get(&prefs) - if err != nil { - return err - } - - if !exists { - prefs = models.Preferences{ - UserId: cmd.UserId, - OrgId: cmd.OrgId, - TeamId: cmd.TeamId, - HomeDashboardId: cmd.HomeDashboardId, - Timezone: cmd.Timezone, - WeekStart: cmd.WeekStart, - Theme: cmd.Theme, - Created: time.Now(), - Updated: time.Now(), - JsonData: &models.PreferencesJsonData{}, - } - - if cmd.Navbar != nil { - prefs.JsonData.Navbar = *cmd.Navbar - } - - if cmd.QueryHistory != nil { - prefs.JsonData.QueryHistory = *cmd.QueryHistory - } - - _, err = sess.Insert(&prefs) - return err - } - // Wrap this in an if statement to maintain backwards compatibility - if cmd.Navbar != nil { - if prefs.JsonData == nil { - prefs.JsonData = &models.PreferencesJsonData{} - } - if cmd.Navbar.SavedItems != nil { - prefs.JsonData.Navbar.SavedItems = cmd.Navbar.SavedItems - } - } - - if cmd.QueryHistory != nil { - if prefs.JsonData == nil { - prefs.JsonData = &models.PreferencesJsonData{} - } - if cmd.QueryHistory.HomeTab != "" { - prefs.JsonData.QueryHistory.HomeTab = cmd.QueryHistory.HomeTab - } - } - - prefs.HomeDashboardId = cmd.HomeDashboardId - prefs.Timezone = cmd.Timezone - prefs.WeekStart = cmd.WeekStart - prefs.Theme = cmd.Theme - prefs.Updated = time.Now() - prefs.Version += 1 - _, err = sess.ID(prefs.Id).AllCols().Update(&prefs) - return err - }) -} - -func (ss *SQLStore) PatchPreferences(ctx context.Context, cmd *models.PatchPreferencesCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - var prefs models.Preferences - exists, err := sess.Where("org_id=? AND user_id=? AND team_id=?", cmd.OrgId, cmd.UserId, cmd.TeamId).Get(&prefs) - if err != nil { - return err - } - - if !exists { - prefs = models.Preferences{ - UserId: cmd.UserId, - OrgId: cmd.OrgId, - TeamId: cmd.TeamId, - Created: time.Now(), - JsonData: &models.PreferencesJsonData{}, - } - } - - if cmd.Navbar != nil { - if prefs.JsonData == nil { - prefs.JsonData = &models.PreferencesJsonData{} - } - if cmd.Navbar.SavedItems != nil { - prefs.JsonData.Navbar.SavedItems = cmd.Navbar.SavedItems - } - } - - if cmd.QueryHistory != nil { - if prefs.JsonData == nil { - prefs.JsonData = &models.PreferencesJsonData{} - } - if cmd.QueryHistory.HomeTab != "" { - prefs.JsonData.QueryHistory.HomeTab = cmd.QueryHistory.HomeTab - } - } - - if cmd.HomeDashboardId != nil { - prefs.HomeDashboardId = *cmd.HomeDashboardId - } - - if cmd.Timezone != nil { - prefs.Timezone = *cmd.Timezone - } - - if cmd.WeekStart != nil { - prefs.WeekStart = *cmd.WeekStart - } - - if cmd.Theme != nil { - prefs.Theme = *cmd.Theme - } - - prefs.Updated = time.Now() - prefs.Version += 1 - - if exists { - _, err = sess.ID(prefs.Id).AllCols().Update(&prefs) - } else { - _, err = sess.Insert(&prefs) - } - return err - }) -} diff --git a/pkg/services/sqlstore/preferences_test.go b/pkg/services/sqlstore/preferences_test.go deleted file mode 100644 index 8ff9f9f073a..00000000000 --- a/pkg/services/sqlstore/preferences_test.go +++ /dev/null @@ -1,302 +0,0 @@ -//go:build integration -// +build integration - -package sqlstore - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/models" -) - -func TestPreferencesDataAccess(t *testing.T) { - ss := InitTestDB(t) - emptyNavbarPreferences := models.NavbarPreference{} - userNavbarPreferences := models.NavbarPreference{ - SavedItems: []models.NavLink{{ - Id: "explore", - Text: "Explore", - Url: "/explore", - }}, - } - orgNavbarPreferences := models.NavbarPreference{ - SavedItems: []models.NavLink{{ - Id: "alerting", - Text: "Alerting", - Url: "/alerting", - }}, - } - team1NavbarPreferences := models.NavbarPreference{ - SavedItems: []models.NavLink{{ - Id: "dashboards", - Text: "Dashboards", - Url: "/dashboards", - }}, - } - team2NavbarPreferences := models.NavbarPreference{ - SavedItems: []models.NavLink{{ - Id: "home", - Text: "Home", - Url: "/home", - }}, - } - - emptyPreferencesJsonData := models.PreferencesJsonData{ - Navbar: emptyNavbarPreferences, - } - userPreferencesJsonData := models.PreferencesJsonData{ - Navbar: userNavbarPreferences, - } - orgPreferencesJsonData := models.PreferencesJsonData{ - Navbar: orgNavbarPreferences, - } - team2PreferencesJsonData := models.PreferencesJsonData{ - Navbar: team2NavbarPreferences, - } - - t.Run("GetPreferencesWithDefaults with no saved preferences should return defaults", func(t *testing.T) { - ss.Cfg.DefaultTheme = "light" - ss.Cfg.DateFormats.DefaultTimezone = "UTC" - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{}} - err := ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, "light", query.Result.Theme) - require.Equal(t, "UTC", query.Result.Timezone) - require.Equal(t, int64(0), query.Result.HomeDashboardId) - require.Equal(t, &emptyPreferencesJsonData, query.Result.JsonData) - }) - - t.Run("GetPreferencesWithDefaults with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 1}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, int64(4), query.Result.HomeDashboardId) - }) - - t.Run("GetPreferencesWithDefaults with saved org and other user home dashboard should return org home dashboard", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 2}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, int64(1), query.Result.HomeDashboardId) - }) - - t.Run("GetPreferencesWithDefaults with saved org and teams home dashboard should return last team home dashboard", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{ - User: &models.SignedInUser{OrgId: 1, Teams: []int64{2, 3}}, - } - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, int64(3), query.Result.HomeDashboardId) - }) - - t.Run("GetPreferencesWithDefaults with saved org and other teams home dashboard should return org home dashboard", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, int64(1), query.Result.HomeDashboardId) - }) - - t.Run("GetPreferencesWithDefaults with saved org, teams and user home dashboard should return user home dashboard", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{ - User: &models.SignedInUser{OrgId: 1, UserId: 1, Teams: []int64{2, 3}}, - } - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, int64(4), query.Result.HomeDashboardId) - }) - - t.Run("GetPreferencesWithDefaults with saved org, other teams and user home dashboard should return org home dashboard", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, HomeDashboardId: 1}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, HomeDashboardId: 2}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, HomeDashboardId: 3}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, HomeDashboardId: 4}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{ - User: &models.SignedInUser{OrgId: 1, UserId: 2}, - } - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, int64(1), query.Result.HomeDashboardId) - }) - - t.Run("GetPreferencesWithDefaults with saved org and user json data should return user json data", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, Navbar: &userNavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 1}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, &userPreferencesJsonData, query.Result.JsonData) - }) - - t.Run("GetPreferencesWithDefaults with saved org and other user json data should return org json data", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, Navbar: &userNavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1, UserId: 2}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, &orgPreferencesJsonData, query.Result.JsonData) - }) - - t.Run("GetPreferencesWithDefaults with saved org and teams json data should return last team json data", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, Navbar: &team1NavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, Navbar: &team2NavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{ - User: &models.SignedInUser{OrgId: 1, Teams: []int64{2, 3}}, - } - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, &team2PreferencesJsonData, query.Result.JsonData) - }) - - t.Run("GetPreferencesWithDefaults with saved org and other teams json data should return org json data", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, Navbar: &team1NavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, Navbar: &team2NavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{OrgId: 1}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, &orgPreferencesJsonData, query.Result.JsonData) - }) - - t.Run("GetPreferencesWithDefaults with saved org, teams and user json data should return user json data", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, Navbar: &team1NavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, Navbar: &team2NavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, Navbar: &userNavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{ - User: &models.SignedInUser{OrgId: 1, UserId: 1, Teams: []int64{2, 3}}, - } - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, &userPreferencesJsonData, query.Result.JsonData) - }) - - t.Run("GetPreferencesWithDefaults with saved org, other teams and user json data should return org json data", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 2, Navbar: &team1NavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, TeamId: 3, Navbar: &team2NavbarPreferences}) - require.NoError(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{OrgId: 1, UserId: 1, Navbar: &userNavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{ - User: &models.SignedInUser{OrgId: 1, UserId: 2}, - } - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - require.Equal(t, &orgPreferencesJsonData, query.Result.JsonData) - }) - - t.Run("SavePreferences for a user should store correct values", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{UserId: models.SignedInUser{}.UserId, Theme: "dark", Timezone: "browser", HomeDashboardId: 5, WeekStart: "1", Navbar: &userNavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - expected := &models.Preferences{ - Id: query.Result.Id, - Version: query.Result.Version, - HomeDashboardId: 5, - Timezone: "browser", - WeekStart: "1", - Theme: "dark", - JsonData: &userPreferencesJsonData, - Created: query.Result.Created, - Updated: query.Result.Updated, - } - if diff := cmp.Diff(expected, query.Result); diff != "" { - t.Fatalf("Result mismatch (-want +got):\n%s", diff) - } - }) - - t.Run("PatchPreferences for a user should only modify a single value", func(t *testing.T) { - err := ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{UserId: models.SignedInUser{}.UserId, Theme: "dark", Timezone: "browser", HomeDashboardId: 5, WeekStart: "1", Navbar: &orgNavbarPreferences}) - require.NoError(t, err) - - err = ss.PatchPreferences(context.Background(), &models.PatchPreferencesCommand{UserId: models.SignedInUser{}.UserId, Navbar: &userNavbarPreferences}) - require.NoError(t, err) - - query := &models.GetPreferencesWithDefaultsQuery{User: &models.SignedInUser{}} - err = ss.GetPreferencesWithDefaults(context.Background(), query) - require.NoError(t, err) - expected := &models.Preferences{ - Id: query.Result.Id, - Version: query.Result.Version, - HomeDashboardId: 5, - Timezone: "browser", - WeekStart: "1", - Theme: "dark", - JsonData: &userPreferencesJsonData, - Created: query.Result.Created, - Updated: query.Result.Updated, - } - if diff := cmp.Diff(expected, query.Result); diff != "" { - t.Fatalf("Result mismatch (-want +got):\n%s", diff) - } - }) -} diff --git a/pkg/services/sqlstore/session.go b/pkg/services/sqlstore/session.go index dfa7c5fbe0a..556b936d4d1 100644 --- a/pkg/services/sqlstore/session.go +++ b/pkg/services/sqlstore/session.go @@ -30,8 +30,8 @@ func (ss *SQLStore) NewSession(ctx context.Context) *DBSession { return sess } -func newSession(ctx context.Context) *DBSession { - sess := &DBSession{Session: x.NewSession()} +func (ss *SQLStore) newSession(ctx context.Context) *DBSession { + sess := &DBSession{Session: ss.engine.NewSession()} sess.Session = sess.Session.Context(ctx) return sess diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index 35e66d58350..8871a68d8fb 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -15,9 +15,9 @@ type Store interface { DeleteExpiredSnapshots(ctx context.Context, cmd *models.DeleteExpiredSnapshotsCommand) error CreateDashboardSnapshot(ctx context.Context, cmd *models.CreateDashboardSnapshotCommand) error DeleteDashboardSnapshot(ctx context.Context, cmd *models.DeleteDashboardSnapshotCommand) error - GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery) error + GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error - SearchDashboardSnapshots(query *models.GetDashboardSnapshotsQuery) error + SearchDashboardSnapshots(ctx context.Context, query *models.GetDashboardSnapshotsQuery) error GetOrgByName(name string) (*models.Org, error) CreateOrgWithMember(name string, userID int64) (models.Org, error) UpdateOrg(ctx context.Context, cmd *models.UpdateOrgCommand) error @@ -60,10 +60,6 @@ type Store interface { GetTeamMembers(ctx context.Context, query *models.GetTeamMembersQuery) error NewSession(ctx context.Context) *DBSession WithDbSession(ctx context.Context, callback DBTransactionFunc) error - GetPreferencesWithDefaults(ctx context.Context, query *models.GetPreferencesWithDefaultsQuery) error - GetPreferences(ctx context.Context, query *models.GetPreferencesQuery) error - SavePreferences(ctx context.Context, cmd *models.SavePreferencesCommand) error - PatchPreferences(ctx context.Context, cmd *models.PatchPreferencesCommand) error GetPluginSettings(ctx context.Context, orgID int64) ([]*models.PluginSetting, error) GetPluginSettingById(ctx context.Context, query *models.GetPluginSettingByIdQuery) error UpdatePluginSetting(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error @@ -149,4 +145,5 @@ type Store interface { HasAdminPermissionInFolders(ctx context.Context, query *models.HasAdminPermissionInFoldersQuery) error GetDashboardPermissionsForUser(ctx context.Context, query *models.GetDashboardPermissionsForUserQuery) error GetDashboardSlugById(ctx context.Context, query *models.GetDashboardSlugByIdQuery) error + IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error } diff --git a/pkg/services/sqlstore/tags_test.go b/pkg/services/sqlstore/tags_test.go index f1b986ded62..6fceef6308c 100644 --- a/pkg/services/sqlstore/tags_test.go +++ b/pkg/services/sqlstore/tags_test.go @@ -13,7 +13,7 @@ import ( ) func TestSavingTags(t *testing.T) { - InitTestDB(t) + ss := InitTestDB(t) tagPairs := []*models.Tag{ {Key: "outage"}, @@ -21,7 +21,7 @@ func TestSavingTags(t *testing.T) { {Key: "server", Value: "server-1"}, {Key: "error"}, } - tags, err := EnsureTagsExist(newSession(context.Background()), tagPairs) + tags, err := EnsureTagsExist(ss.newSession(context.Background()), tagPairs) require.Nil(t, err) require.Equal(t, 4, len(tags)) diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index 46c88f8f0bc..d78c1aa873e 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -176,129 +176,133 @@ func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession } func (ss *SQLStore) SearchTeams(ctx context.Context, query *models.SearchTeamsQuery) error { - query.Result = models.SearchTeamQueryResult{ - Teams: make([]*models.TeamDTO, 0), - } - queryWithWildcards := "%" + query.Query + "%" + return ss.WithDbSession(ctx, func(sess *DBSession) error { + query.Result = models.SearchTeamQueryResult{ + Teams: make([]*models.TeamDTO, 0), + } + queryWithWildcards := "%" + query.Query + "%" - var sql bytes.Buffer - params := make([]interface{}, 0) + var sql bytes.Buffer + params := make([]interface{}, 0) - filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers) - for _, user := range filteredUsers { - params = append(params, user) - } + filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers) + for _, user := range filteredUsers { + params = append(params, user) + } - if query.UserIdFilter == models.FilterIgnoreUser { - sql.WriteString(getTeamSelectSQLBase(filteredUsers)) - } else { - sql.WriteString(getTeamSelectWithPermissionsSQLBase(filteredUsers)) - params = append(params, query.UserIdFilter) - } + if query.UserIdFilter == models.FilterIgnoreUser { + sql.WriteString(getTeamSelectSQLBase(filteredUsers)) + } else { + sql.WriteString(getTeamSelectWithPermissionsSQLBase(filteredUsers)) + params = append(params, query.UserIdFilter) + } - sql.WriteString(` WHERE team.org_id = ?`) - params = append(params, query.OrgId) + sql.WriteString(` WHERE team.org_id = ?`) + params = append(params, query.OrgId) - if query.Query != "" { - sql.WriteString(` and team.name ` + dialect.LikeStr() + ` ?`) - params = append(params, queryWithWildcards) - } + if query.Query != "" { + sql.WriteString(` and team.name ` + ss.Dialect.LikeStr() + ` ?`) + params = append(params, queryWithWildcards) + } - if query.Name != "" { - sql.WriteString(` and team.name = ?`) - params = append(params, query.Name) - } + if query.Name != "" { + sql.WriteString(` and team.name = ?`) + params = append(params, query.Name) + } - var ( - acFilter ac.SQLFilter - err error - ) - if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { - acFilter, err = ac.Filter(query.SignedInUser, "team.id", "teams:id:", ac.ActionTeamsRead) - if err != nil { + var ( + acFilter ac.SQLFilter + err error + ) + if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { + acFilter, err = ac.Filter(query.SignedInUser, "team.id", "teams:id:", ac.ActionTeamsRead) + if err != nil { + return err + } + sql.WriteString(` and` + acFilter.Where) + params = append(params, acFilter.Args...) + } + + sql.WriteString(` order by team.name asc`) + + if query.Limit != 0 { + offset := query.Limit * (query.Page - 1) + sql.WriteString(ss.Dialect.LimitOffset(int64(query.Limit), int64(offset))) + } + + if err := sess.SQL(sql.String(), params...).Find(&query.Result.Teams); err != nil { return err } - sql.WriteString(` and` + acFilter.Where) - params = append(params, acFilter.Args...) - } - sql.WriteString(` order by team.name asc`) + team := models.Team{} + countSess := sess.Table("team") + countSess.Where("team.org_id=?", query.OrgId) - if query.Limit != 0 { - offset := query.Limit * (query.Page - 1) - sql.WriteString(dialect.LimitOffset(int64(query.Limit), int64(offset))) - } + if query.Query != "" { + countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards) + } - if err := x.SQL(sql.String(), params...).Find(&query.Result.Teams); err != nil { - return err - } + if query.Name != "" { + countSess.Where("name=?", query.Name) + } - team := models.Team{} - countSess := x.Table("team") - countSess.Where("team.org_id=?", query.OrgId) - - if query.Query != "" { - countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards) - } - - if query.Name != "" { - countSess.Where("name=?", query.Name) - } - - // If we're not retrieving all results, then only search for teams that this user has access to - if query.UserIdFilter != models.FilterIgnoreUser { - countSess. - Where(` + // If we're not retrieving all results, then only search for teams that this user has access to + if query.UserIdFilter != models.FilterIgnoreUser { + countSess. + Where(` team.id IN ( SELECT team_id FROM team_member WHERE team_member.user_id = ? )`, query.UserIdFilter) - } + } - // Only count teams user can see - if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { - countSess.Where(acFilter.Where, acFilter.Args...) - } + // Only count teams user can see + if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { + countSess.Where(acFilter.Where, acFilter.Args...) + } - count, err := countSess.Count(&team) - query.Result.TotalCount = count + count, err := countSess.Count(&team) + query.Result.TotalCount = count - return err + return err + }) } func (ss *SQLStore) GetTeamById(ctx context.Context, query *models.GetTeamByIdQuery) error { - var sql bytes.Buffer - params := make([]interface{}, 0) + return ss.WithDbSession(ctx, func(sess *DBSession) error { + var sql bytes.Buffer + params := make([]interface{}, 0) - filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers) - sql.WriteString(getTeamSelectSQLBase(filteredUsers)) - for _, user := range filteredUsers { - params = append(params, user) - } + filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers) + sql.WriteString(getTeamSelectSQLBase(filteredUsers)) + for _, user := range filteredUsers { + params = append(params, user) + } - if query.UserIdFilter != models.FilterIgnoreUser { - sql.WriteString(` INNER JOIN team_member ON team.id = team_member.team_id AND team_member.user_id = ?`) - params = append(params, query.UserIdFilter) - } + if query.UserIdFilter != models.FilterIgnoreUser { + sql.WriteString(` INNER JOIN team_member ON team.id = team_member.team_id AND team_member.user_id = ?`) + params = append(params, query.UserIdFilter) + } - sql.WriteString(` WHERE team.org_id = ? and team.id = ?`) - params = append(params, query.OrgId, query.Id) + sql.WriteString(` WHERE team.org_id = ? and team.id = ?`) + params = append(params, query.OrgId, query.Id) - var team models.TeamDTO - exists, err := x.SQL(sql.String(), params...).Get(&team) + var team models.TeamDTO + exists, err := sess.SQL(sql.String(), params...).Get(&team) - if err != nil { - return err - } + if err != nil { + return err + } - if !exists { - return models.ErrTeamNotFound - } + if !exists { + return models.ErrTeamNotFound + } - query.Result = &team - return nil + query.Result = &team + return nil + }) } // GetTeamsByUser is used by the Guardian when checking a users' permissions @@ -513,7 +517,7 @@ func (ss *SQLStore) GetTeamMembers(ctx context.Context, query *models.GetTeamMem // Note we assume that checking SignedInUser is allowed to see team members for this team has already been performed // If the signed in user is not set no member will be returned if ss.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) { - sqlID := fmt.Sprintf("%s.%s", x.Dialect().Quote("user"), x.Dialect().Quote("id")) + sqlID := fmt.Sprintf("%s.%s", ss.engine.Dialect().Quote("user"), ss.engine.Dialect().Quote("id")) *acFilter, err = ac.Filter(query.SignedInUser, sqlID, "users:id:", ac.ActionOrgUsersRead) if err != nil { return err @@ -525,67 +529,71 @@ func (ss *SQLStore) GetTeamMembers(ctx context.Context, query *models.GetTeamMem // getTeamMembers return a list of members for the specified team func (ss *SQLStore) getTeamMembers(ctx context.Context, query *models.GetTeamMembersQuery, acUserFilter *ac.SQLFilter) error { - query.Result = make([]*models.TeamMemberDTO, 0) - sess := x.Table("team_member") - sess.Join("INNER", x.Dialect().Quote("user"), - fmt.Sprintf("team_member.user_id=%s.%s", x.Dialect().Quote("user"), x.Dialect().Quote("id")), - ) + return ss.WithDbSession(ctx, func(dbSess *DBSession) error { + query.Result = make([]*models.TeamMemberDTO, 0) + sess := dbSess.Table("team_member") + sess.Join("INNER", ss.Dialect.Quote("user"), + fmt.Sprintf("team_member.user_id=%s.%s", ss.Dialect.Quote("user"), ss.Dialect.Quote("id")), + ) - if acUserFilter != nil { - sess.Where(acUserFilter.Where, acUserFilter.Args...) - } + if acUserFilter != nil { + sess.Where(acUserFilter.Where, acUserFilter.Args...) + } - // Join with only most recent auth module - authJoinCondition := `( + // Join with only most recent auth module + authJoinCondition := `( SELECT id from user_auth WHERE user_auth.user_id = team_member.user_id ORDER BY user_auth.created DESC ` - authJoinCondition = "user_auth.id=" + authJoinCondition + dialect.Limit(1) + ")" - sess.Join("LEFT", "user_auth", authJoinCondition) + authJoinCondition = "user_auth.id=" + authJoinCondition + ss.Dialect.Limit(1) + ")" + sess.Join("LEFT", "user_auth", authJoinCondition) - if query.OrgId != 0 { - sess.Where("team_member.org_id=?", query.OrgId) - } - if query.TeamId != 0 { - sess.Where("team_member.team_id=?", query.TeamId) - } - if query.UserId != 0 { - sess.Where("team_member.user_id=?", query.UserId) - } - if query.External { - sess.Where("team_member.external=?", dialect.BooleanStr(true)) - } - sess.Cols( - "team_member.org_id", - "team_member.team_id", - "team_member.user_id", - "user.email", - "user.name", - "user.login", - "team_member.external", - "team_member.permission", - "user_auth.auth_module", - ) - sess.Asc("user.login", "user.email") + if query.OrgId != 0 { + sess.Where("team_member.org_id=?", query.OrgId) + } + if query.TeamId != 0 { + sess.Where("team_member.team_id=?", query.TeamId) + } + if query.UserId != 0 { + sess.Where("team_member.user_id=?", query.UserId) + } + if query.External { + sess.Where("team_member.external=?", ss.Dialect.BooleanStr(true)) + } + sess.Cols( + "team_member.org_id", + "team_member.team_id", + "team_member.user_id", + "user.email", + "user.name", + "user.login", + "team_member.external", + "team_member.permission", + "user_auth.auth_module", + ) + sess.Asc("user.login", "user.email") - err := sess.Find(&query.Result) - return err -} - -func IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error { - builder := &SQLBuilder{} - builder.Write("SELECT COUNT(team.id) AS count FROM team INNER JOIN team_member ON team_member.team_id = team.id WHERE team.org_id = ? AND team_member.user_id = ? AND team_member.permission = ?", query.SignedInUser.OrgId, query.SignedInUser.UserId, models.PERMISSION_ADMIN) - - type teamCount struct { - Count int64 - } - - resp := make([]*teamCount, 0) - if err := x.SQL(builder.GetSQLString(), builder.params...).Find(&resp); err != nil { + err := sess.Find(&query.Result) return err - } + }) +} - query.Result = len(resp) > 0 && resp[0].Count > 0 +func (ss *SQLStore) IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error { + return ss.WithDbSession(ctx, func(sess *DBSession) error { + builder := &SQLBuilder{} + builder.Write("SELECT COUNT(team.id) AS count FROM team INNER JOIN team_member ON team_member.team_id = team.id WHERE team.org_id = ? AND team_member.user_id = ? AND team_member.permission = ?", query.SignedInUser.OrgId, query.SignedInUser.UserId, models.PERMISSION_ADMIN) - return nil + type teamCount struct { + Count int64 + } + + resp := make([]*teamCount, 0) + if err := sess.SQL(builder.GetSQLString(), builder.params...).Find(&resp); err != nil { + return err + } + + query.Result = len(resp) > 0 && resp[0].Count > 0 + + return nil + }) } diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go index 23445ccbfe4..63bdd2b76f7 100644 --- a/pkg/services/sqlstore/team_test.go +++ b/pkg/services/sqlstore/team_test.go @@ -302,12 +302,12 @@ func TestTeamCommandsAndQueries(t *testing.T) { require.NoError(t, err) query := &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgID, UserId: userIds[0]}} - err = IsAdminOfTeams(context.Background(), query) + err = sqlStore.IsAdminOfTeams(context.Background(), query) require.NoError(t, err) require.False(t, query.Result) query = &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgID, UserId: userIds[1]}} - err = IsAdminOfTeams(context.Background(), query) + err = sqlStore.IsAdminOfTeams(context.Background(), query) require.NoError(t, err) require.True(t, query.Result) }) diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index e2340de0ab6..f91e70134ff 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -245,11 +245,6 @@ func TestUserDataAccess(t *testing.T) { }) require.Nil(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{ - UserId: users[1].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark", - }) - require.Nil(t, err) - // When the user is deleted err = ss.DeleteUser(context.Background(), &models.DeleteUserCommand{UserId: users[1].Id}) require.Nil(t, err) @@ -266,13 +261,6 @@ func TestUserDataAccess(t *testing.T) { require.Len(t, permQuery.Result, 0) - prefsQuery := &models.GetPreferencesQuery{OrgId: users[0].OrgId, UserId: users[1].Id} - err = ss.GetPreferences(context.Background(), prefsQuery) - require.Nil(t, err) - - require.EqualValues(t, prefsQuery.Result.OrgId, 0) - require.EqualValues(t, prefsQuery.Result.UserId, 0) - // A user is an org member and has been assigned permissions // Re-init DB ss = InitTestDB(t) @@ -296,11 +284,6 @@ func TestUserDataAccess(t *testing.T) { }) require.Nil(t, err) - err = ss.SavePreferences(context.Background(), &models.SavePreferencesCommand{ - UserId: users[1].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark", - }) - require.Nil(t, err) - ss.CacheService.Flush() query3 := &models.GetSignedInUserQuery{OrgId: users[1].OrgId, UserId: users[1].Id} @@ -351,13 +334,6 @@ func TestUserDataAccess(t *testing.T) { require.Nil(t, err) require.Len(t, permQuery.Result, 0) - - prefsQuery = &models.GetPreferencesQuery{OrgId: users[0].OrgId, UserId: users[1].Id} - err = ss.GetPreferences(context.Background(), prefsQuery) - require.Nil(t, err) - - require.EqualValues(t, prefsQuery.Result.OrgId, 0) - require.EqualValues(t, prefsQuery.Result.UserId, 0) }) ss = InitTestDB(t) diff --git a/pkg/services/store/storage_disk.go b/pkg/services/store/storage_disk.go index 143a80b5e28..78955451da7 100644 --- a/pkg/services/store/storage_disk.go +++ b/pkg/services/store/storage_disk.go @@ -57,7 +57,7 @@ func newDiskStorage(prefix string, name string, cfg *StorageLocalDiskConfig) *ro } else { s.store = filestorage.NewCdkBlobStorage(grafanaStorageLogger, bucket, "", - filestorage.NewPathFilters(cfg.Roots, nil, nil, nil)) + filestorage.NewPathFilter(cfg.Roots, nil, nil, nil)) meta.Ready = true // exists! } diff --git a/pkg/services/thumbs/crawler.go b/pkg/services/thumbs/crawler.go index c641455728c..7e65a3590cf 100644 --- a/pkg/services/thumbs/crawler.go +++ b/pkg/services/thumbs/crawler.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/grafana/grafana/pkg/setting" "golang.org/x/sync/errgroup" "github.com/grafana/grafana/pkg/infra/log" @@ -19,8 +20,10 @@ import ( ) type simpleCrawler struct { - renderService rendering.Service - threadCount int + renderService rendering.Service + threadCount int + concurrentLimit int + renderingTimeout time.Duration glive *live.GrafanaLive thumbnailRepo thumbnailRepo @@ -36,13 +39,17 @@ type simpleCrawler struct { renderingSessionByOrgId map[int64]rendering.Session } -func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo) dashRenderer { +func newSimpleCrawler(renderService rendering.Service, gl *live.GrafanaLive, repo thumbnailRepo, cfg *setting.Cfg, settings setting.DashboardPreviewsSettings) dashRenderer { + threadCount := int(settings.CrawlThreadCount) c := &simpleCrawler{ - renderService: renderService, - threadCount: 6, - glive: gl, - thumbnailRepo: repo, - log: log.New("thumbnails_crawler"), + // temporarily increases the concurrentLimit from the 'cfg.RendererConcurrentRequestLimit' to 'cfg.RendererConcurrentRequestLimit + crawlerThreadCount' + concurrentLimit: cfg.RendererConcurrentRequestLimit + threadCount, + renderingTimeout: settings.RenderingTimeout, + renderService: renderService, + threadCount: threadCount, + glive: gl, + thumbnailRepo: repo, + log: log.New("thumbnails_crawler"), status: crawlStatus{ State: initializing, Complete: 0, @@ -154,11 +161,11 @@ func (r *simpleCrawler) Run(ctx context.Context, auth CrawlerAuth, mode CrawlerM r.auth = auth r.opts = rendering.Opts{ TimeoutOpts: rendering.TimeoutOpts{ - Timeout: 20 * time.Second, + Timeout: r.renderingTimeout, RequestTimeoutMultiplier: 3, }, Theme: theme, - ConcurrentLimit: 10, + ConcurrentLimit: r.concurrentLimit, } r.renderingSessionByOrgId = make(map[int64]rendering.Session) diff --git a/pkg/services/thumbs/service.go b/pkg/services/thumbs/service.go index 45cba2828aa..72e0ccb450f 100644 --- a/pkg/services/thumbs/service.go +++ b/pkg/services/thumbs/service.go @@ -51,6 +51,8 @@ type thumbService struct { crawlLockServiceActionName string log log.Logger usageStatsService usagestats.Service + canRunCrawler bool + settings setting.DashboardPreviewsSettings } type crawlerScheduleOptions struct { @@ -67,30 +69,34 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, lockS if !features.IsEnabled(featuremgmt.FlagDashboardPreviews) { return &dummyService{} } - logger := log.New("thumbnails_service") + logger := log.New("previews_service") + logger.Info("initialized thumb", "settings", cfg.DashboardPreviews) thumbnailRepo := newThumbnailRepo(store) + canRunCrawler := true crawlerAuth, err := authSetupService.Setup(context.Background()) if err != nil { - logger.Error("Failed to setup auth for the dashboard previews crawler", "err", err) - return &dummyService{} + logger.Error("failed to setup auth for the dashboard previews crawler", "err", err) + canRunCrawler = false } t := &thumbService{ usageStatsService: usageStatsService, renderingService: renderService, - renderer: newSimpleCrawler(renderService, gl, thumbnailRepo), + renderer: newSimpleCrawler(renderService, gl, thumbnailRepo, cfg, cfg.DashboardPreviews), thumbnailRepo: thumbnailRepo, store: store, features: features, lockService: lockService, crawlLockServiceActionName: "dashboard-crawler", log: logger, + canRunCrawler: canRunCrawler, + settings: cfg.DashboardPreviews, scheduleOptions: crawlerScheduleOptions{ - tickerInterval: time.Hour, - crawlInterval: time.Hour * 12, - maxCrawlDuration: time.Hour, + tickerInterval: 5 * time.Minute, + crawlInterval: cfg.DashboardPreviews.SchedulerInterval, + maxCrawlDuration: cfg.DashboardPreviews.MaxCrawlDuration, crawlerMode: CrawlerModeThumbs, thumbnailKind: models.ThumbnailKindDefault, themes: []models.Theme{models.ThemeDark, models.ThemeLight}, @@ -401,6 +407,10 @@ func (hs *thumbService) getDashboardId(c *models.ReqContext, uid string) (int64, } func (hs *thumbService) runOnDemandCrawl(parentCtx context.Context, theme models.Theme, mode CrawlerMode, kind models.ThumbnailKind, authOpts rendering.AuthOpts) { + if !hs.canRunCrawler { + return + } + crawlerCtx, cancel := context.WithTimeout(parentCtx, hs.scheduleOptions.maxCrawlDuration) defer cancel() @@ -435,7 +445,7 @@ func (hs *thumbService) runScheduledCrawl(parentCtx context.Context) { } func (hs *thumbService) Run(ctx context.Context) error { - if !hs.features.IsEnabled(featuremgmt.FlagDashboardPreviewsScheduler) { + if !hs.canRunCrawler { return nil } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 58d695f41fc..43974b76986 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -439,6 +439,8 @@ type Cfg struct { // Query history QueryHistoryEnabled bool + + DashboardPreviews DashboardPreviewsSettings } type CommandLineArgs struct { @@ -1001,6 +1003,8 @@ func (cfg *Cfg) Load(args CommandLineArgs) error { cfg.readDataSourcesSettings() + cfg.DashboardPreviews = readDashboardPreviewsSettings(iniFile) + if VerifyEmailEnabled && !cfg.Smtp.Enabled { cfg.Logger.Warn("require_email_validation is enabled but smtp is disabled") } diff --git a/pkg/setting/setting_dashboard_previews.go b/pkg/setting/setting_dashboard_previews.go new file mode 100644 index 00000000000..f73e046e99f --- /dev/null +++ b/pkg/setting/setting_dashboard_previews.go @@ -0,0 +1,31 @@ +package setting + +import ( + "time" + + "gopkg.in/ini.v1" +) + +type DashboardPreviewsSettings struct { + SchedulerInterval time.Duration + MaxCrawlDuration time.Duration + RenderingTimeout time.Duration + CrawlThreadCount uint32 +} + +func readDashboardPreviewsSettings(iniFile *ini.File) DashboardPreviewsSettings { + maxThreadCount := uint32(20) + + s := DashboardPreviewsSettings{} + + previewsCrawlerSection := iniFile.Section("dashboard_previews.crawler") + s.CrawlThreadCount = uint32(previewsCrawlerSection.Key("thread_count").MustUint(6)) + if s.CrawlThreadCount > maxThreadCount { + s.CrawlThreadCount = maxThreadCount + } + + s.SchedulerInterval = previewsCrawlerSection.Key("scheduler_interval").MustDuration(12 * time.Hour) + s.MaxCrawlDuration = previewsCrawlerSection.Key("max_crawl_duration").MustDuration(1 * time.Hour) + s.RenderingTimeout = previewsCrawlerSection.Key("rendering_timeout").MustDuration(20 * time.Second) + return s +} diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index 495656e115e..796c16a5a58 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -2087,7 +2087,7 @@ var expEmailNotifications = []*models.SendEmailCommandSync{ Annotations: template.KV{}, StartsAt: time.Time{}, EndsAt: time.Time{}, - GeneratorURL: "http://localhost:3000/alerting/UID_EmailAlert/edit", + GeneratorURL: "http://localhost:3000/alerting/grafana/UID_EmailAlert/view", Fingerprint: "08c220aa26cd0cf5", SilenceURL: "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DEmailAlert", DashboardURL: "", @@ -2147,7 +2147,7 @@ var expNonEmailNotifications = map[string][]string{ { "title": "[FIRING:1] SlackAlert2 ", "title_link": "http://localhost:3000/alerting/list", - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = SlackAlert2\nAnnotations:\nSource: http://localhost:3000/alerting/UID_SlackAlert2/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSlackAlert2\n", + "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = SlackAlert2\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SlackAlert2/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSlackAlert2\n", "fallback": "[FIRING:1] SlackAlert2 ", "footer": "Grafana v", "footer_icon": "https://grafana.com/assets/img/fav32.png", @@ -2180,7 +2180,7 @@ var expNonEmailNotifications = map[string][]string{ "component": "Integration Test", "group": "testgroup", "custom_details": { - "firing": "\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = PagerdutyAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_PagerdutyAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DPagerdutyAlert\n", + "firing": "\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = PagerdutyAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PagerdutyAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DPagerdutyAlert\n", "num_firing": "1", "num_resolved": "0", "resolved": "" @@ -2200,7 +2200,7 @@ var expNonEmailNotifications = map[string][]string{ `{ "link": { "messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist", - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = DingDingAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_DingDingAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDingDingAlert\n", + "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = DingDingAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DingDingAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDingDingAlert\n", "title": "[FIRING:1] DingDingAlert " }, "msgtype": "link" @@ -2225,7 +2225,7 @@ var expNonEmailNotifications = map[string][]string{ ], "sections": [ { - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = TeamsAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_TeamsAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTeamsAlert\n", + "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = TeamsAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TeamsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTeamsAlert\n", "title": "Details" } ], @@ -2249,7 +2249,7 @@ var expNonEmailNotifications = map[string][]string{ "startsAt": "%s", "valueString": "[ var='A' labels={} value=1 ]", "endsAt": "0001-01-01T00:00:00Z", - "generatorURL": "http://localhost:3000/alerting/UID_WebhookAlert/edit", + "generatorURL": "http://localhost:3000/alerting/grafana/UID_WebhookAlert/view", "fingerprint": "929467973978d053", "silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert", "dashboardURL": "", @@ -2269,12 +2269,12 @@ var expNonEmailNotifications = map[string][]string{ "truncatedAlerts": 0, "title": "[FIRING:1] WebhookAlert ", "state": "alerting", - "message": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = WebhookAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_WebhookAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert\n" + "message": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = WebhookAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_WebhookAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert\n" }`, }, "discord_recv/discord_test": { `{ - "content": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = DiscordAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_DiscordAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDiscordAlert\n", + "content": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = DiscordAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DiscordAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDiscordAlert\n", "embeds": [ { "color": 14037554, @@ -2302,7 +2302,7 @@ var expNonEmailNotifications = map[string][]string{ }, "name": "default" }, - "output": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = SensuGoAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_SensuGoAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSensuGoAlert\n", + "output": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = SensuGoAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SensuGoAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSensuGoAlert\n", "status": 2 }, "entity": { @@ -2315,10 +2315,10 @@ var expNonEmailNotifications = map[string][]string{ }`, }, "pushover_recv/pushover_test": { - "--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert \r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = PushoverAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_PushoverAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DPushoverAlert\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n", + "--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert \r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = PushoverAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PushoverAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DPushoverAlert\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n", }, "telegram_recv/bot6sh027hs034h": { - "--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nhtml\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = TelegramAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_TelegramAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTelegramAlert\n\r\n--abcd--\r\n", + "--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nhtml\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = TelegramAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TelegramAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTelegramAlert\n\r\n--abcd--\r\n", }, "googlechat_recv/googlechat_test": { `{ @@ -2334,7 +2334,7 @@ var expNonEmailNotifications = map[string][]string{ "widgets": [ { "textParagraph": { - "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = GoogleChatAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_GoogleChatAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DGoogleChatAlert\n" + "text": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = GoogleChatAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_GoogleChatAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DGoogleChatAlert\n" } }, { @@ -2372,7 +2372,7 @@ var expNonEmailNotifications = map[string][]string{ "client": "Grafana", "client_url": "http://localhost:3000/alerting/list", "description": "[FIRING:1] KafkaAlert ", - "details": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = KafkaAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_KafkaAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DKafkaAlert\n", + "details": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = KafkaAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_KafkaAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DKafkaAlert\n", "incident_key": "35c0bdb1715f9162a20d7b2a01cb2e3a4c5b1dc663571701e3f67212b696332f" } } @@ -2380,10 +2380,10 @@ var expNonEmailNotifications = map[string][]string{ }`, }, "line_recv/line_test": { - `message=%5BFIRING%3A1%5D+LineAlert+%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5B+var%3D%27A%27+labels%3D%7B%7D+value%3D1+%5D%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2FUID_LineAlert%2Fedit%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DLineAlert%0A`, + `message=%5BFIRING%3A1%5D+LineAlert+%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5B+var%3D%27A%27+labels%3D%7B%7D+value%3D1+%5D%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_LineAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DLineAlert%0A`, }, "threema_recv/threema_test": { - `from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5B+var%3D%27A%27+labels%3D%7B%7D+value%3D1+%5D%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2FUID_ThreemaAlert%2Fedit%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DThreemaAlert%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`, + `from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+%5B+var%3D%27A%27+labels%3D%7B%7D+value%3D1+%5D%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_ThreemaAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DThreemaAlert%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`, }, "victorops_recv/victorops_test": { `{ @@ -2392,14 +2392,14 @@ var expNonEmailNotifications = map[string][]string{ "entity_id": "633ae988fa7074bcb51f3d1c5fef2ba1c5c4ccb45b3ecbf681f7d507b078b1ae", "message_type": "CRITICAL", "monitoring_tool": "Grafana v", - "state_message": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = VictorOpsAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_VictorOpsAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DVictorOpsAlert\n", + "state_message": "**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = VictorOpsAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_VictorOpsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DVictorOpsAlert\n", "timestamp": %s }`, }, "opsgenie_recv/opsgenie_test": { `{ "alias": "47e92f0f6ef9fe99f3954e0d6155f8d09c4b9a038d8c3105e82c0cee4c62956e", - "description": "[FIRING:1] OpsGenieAlert \nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = OpsGenieAlert\nAnnotations:\nSource: http://localhost:3000/alerting/UID_OpsGenieAlert/edit\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DOpsGenieAlert\n", + "description": "[FIRING:1] OpsGenieAlert \nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: [ var='A' labels={} value=1 ]\nLabels:\n - alertname = OpsGenieAlert\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_OpsGenieAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DOpsGenieAlert\n", "details": { "url": "http://localhost:3000/alerting/list" }, @@ -2421,7 +2421,7 @@ var expNonEmailNotifications = map[string][]string{ }, "startsAt": "%s", "endsAt": "0001-01-01T00:00:00Z", - "generatorURL": "http://localhost:3000/alerting/UID_AlertmanagerAlert/edit", + "generatorURL": "http://localhost:3000/alerting/grafana/UID_AlertmanagerAlert/view", "UpdatedAt": "%s", "Timeout": false } diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index ad7d5f3029a..251a62ca3d5 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -12,15 +12,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/ini.v1" + "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/ini.v1" ) // StartGrafana starts a Grafana server. diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go index 84279f4de67..9f256972543 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go @@ -368,6 +368,10 @@ func getQueryUrl(query *types.AzureMonitorQuery, azurePortalUrl string) (string, return "", err } escapedChart := url.QueryEscape(string(chartDef)) + // Azure Portal will timeout if the chart definition includes a space character encoded as '+'. + // url.QueryEscape encodes spaces as '+'. + // Note: this will not encode '+' literals as those are already encoded as '%2B' by url.QueryEscape + escapedChart = strings.ReplaceAll(escapedChart, "+", "%20") return fmt.Sprintf("%s/#blade/Microsoft_Azure_MonitoringMetrics/Metrics.ReactView/Referer/MetricsExplorer/TimeContext/%s/ChartDefinition/%s", azurePortalUrl, escapedTime, escapedChart), nil } diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go index 89faac286c0..abc44521375 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource_test.go @@ -188,7 +188,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) { expected := `http://ds/#blade/Microsoft_Azure_MonitoringMetrics/Metrics.ReactView/Referer/MetricsExplorer/` + `TimeContext/%7B%22absolute%22%3A%7B%22startTime%22%3A%222018-03-15T13%3A00%3A00Z%22%2C%22endTime%22%3A%222018-03-15T13%3A34%3A00Z%22%7D%7D/` + `ChartDefinition/%7B%22v2charts%22%3A%5B%7B%22metrics%22%3A%5B%7B%22resourceMetadata%22%3A%7B%22id%22%3A%22%2Fsubscriptions%2F12345678-aaaa-bbbb-cccc-123456789abc%2FresourceGroups%2Fgrafanastaging%2Fproviders%2FMicrosoft.Compute%2FvirtualMachines%2Fgrafana%22%7D%2C` + - `%22name%22%3A%22Percentage+CPU%22%2C%22aggregationType%22%3A4%2C%22namespace%22%3A%22Microsoft.Compute-virtualMachines%22%2C%22metricVisualization%22%3A%7B%22displayName%22%3A%22Percentage+CPU%22%2C%22resourceDisplayName%22%3A%22grafana%22%7D%7D%5D%7D%5D%7D` + `%22name%22%3A%22Percentage%20CPU%22%2C%22aggregationType%22%3A4%2C%22namespace%22%3A%22Microsoft.Compute-virtualMachines%22%2C%22metricVisualization%22%3A%7B%22displayName%22%3A%22Percentage%20CPU%22%2C%22resourceDisplayName%22%3A%22grafana%22%7D%7D%5D%7D%5D%7D` actual, err := getQueryUrl(queries[0], "http://ds") require.NoError(t, err) require.Equal(t, expected, actual) diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index 72c12e6dc17..b386e6e1d3c 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -136,3 +136,228 @@ func TestTimeSeriesQuery(t *testing.T) { assert.EqualError(t, err, "invalid time range: start time must be before end time") }) } + +type queryDimensions struct { + InstanceID []string `json:"InstanceId,omitempty"` +} + +type queryParameters struct { + MetricQueryType metricQueryType `json:"metricQueryType"` + MetricEditorMode metricEditorMode `json:"metricEditorMode"` + Dimensions queryDimensions `json:"dimensions"` + Expression string `json:"expression"` + Alias string `json:"alias"` + Statistic string `json:"statistic"` + Period string `json:"period"` + MatchExact bool `json:"matchExact"` + MetricName string `json:"metricName"` +} + +var queryId = "query id" + +func newTestQuery(t testing.TB, p queryParameters) json.RawMessage { + t.Helper() + + tsq := struct { + Type string `json:"type"` + MetricQueryType metricQueryType `json:"metricQueryType"` + MetricEditorMode metricEditorMode `json:"metricEditorMode"` + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` + Dimensions struct { + InstanceID []string `json:"InstanceId,omitempty"` + } `json:"dimensions"` + Expression string `json:"expression"` + Region string `json:"region"` + ID string `json:"id"` + Alias string `json:"alias"` + Statistic string `json:"statistic"` + Period string `json:"period"` + MatchExact bool `json:"matchExact"` + RefID string `json:"refId"` + }{ + Type: "timeSeriesQuery", + Region: "us-east-2", + ID: queryId, + RefID: "A", + + MatchExact: p.MatchExact, + MetricQueryType: p.MetricQueryType, + MetricEditorMode: p.MetricEditorMode, + Dimensions: p.Dimensions, + Expression: p.Expression, + Alias: p.Alias, + Statistic: p.Statistic, + Period: p.Period, + MetricName: p.MetricName, + } + + marshalled, err := json.Marshal(tsq) + require.NoError(t, err) + + return marshalled +} + +func Test_QueryData_response_data_frame_names(t *testing.T) { + origNewCWClient := NewCWClient + t.Cleanup(func() { + NewCWClient = origNewCWClient + }) + var cwClient fakeCWClient + NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI { + return &cwClient + } + labelFromGetMetricData := "some label" + cwClient = fakeCWClient{ + GetMetricDataOutput: cloudwatch.GetMetricDataOutput{ + MetricDataResults: []*cloudwatch.MetricDataResult{ + {StatusCode: aws.String("Complete"), Id: aws.String(queryId), Label: aws.String(labelFromGetMetricData), + Values: []*float64{aws.Float64(1.0)}, Timestamps: []*time.Time{{}}}, + }, + }, + } + im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { + return datasourceInfo{}, nil + }) + executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}) + + t.Run("where user defines search expression and alias is defined, then frame name prioritizes period and stat from expression over input", func(t *testing.T) { + query := newTestQuery(t, queryParameters{ + MetricQueryType: MetricQueryTypeSearch, // contributes to isUserDefinedSearchExpression = true + MetricEditorMode: MetricEditorModeRaw, // contributes to isUserDefinedSearchExpression = true + Alias: "{{period}} {{stat}}", + Expression: `SEARCH('{AWS/EC2,InstanceId} MetricName="CPUUtilization"', 'Average', 300)`, // period 300 and stat 'Average' parsed from this expression + Statistic: "Maximum", // stat parsed from expression takes precedence over 'Maximum' + Period: "1200", // period parsed from expression takes precedence over 1200 + }) + + resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + RefID: "A", + TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + JSON: query, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "300 Average", resp.Responses["A"].Frames[0].Name) + }) + + t.Run("where no alias is provided and query is math expression, then frame name is queryId", func(t *testing.T) { + query := newTestQuery(t, queryParameters{ + MetricQueryType: MetricQueryTypeSearch, + MetricEditorMode: MetricEditorModeRaw, + }) + + resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + RefID: "A", + TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + JSON: query, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, queryId, resp.Responses["A"].Frames[0].Name) + }) + + t.Run("where no alias provided and query type is MetricQueryTypeQuery, then frame name is label", func(t *testing.T) { + query := newTestQuery(t, queryParameters{ + MetricQueryType: MetricQueryTypeQuery, + }) + + resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + RefID: "A", + TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + JSON: query, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, labelFromGetMetricData, resp.Responses["A"].Frames[0].Name) + }) + + // where query is inferred search expression and not multivalued dimension expression, then frame name is label + testCasesReturningLabel := map[string]queryParameters{ + "with specific dimensions, matchExact false": {Dimensions: queryDimensions{[]string{"some-instance"}}, MatchExact: false}, + "with wildcard dimensions, matchExact false": {Dimensions: queryDimensions{[]string{"*"}}, MatchExact: false}, + "with wildcard dimensions, matchExact true": {Dimensions: queryDimensions{[]string{"*"}}, MatchExact: true}, + "no dimension, matchExact false": {Dimensions: queryDimensions{}, MatchExact: false}, + } + for name, parameters := range testCasesReturningLabel { + t.Run(name, func(t *testing.T) { + query := newTestQuery(t, parameters) + + resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + RefID: "A", + TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + JSON: query, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, labelFromGetMetricData, resp.Responses["A"].Frames[0].Name) + }) + } + + // complementary test cases to above return default of "metricName_stat" + testCasesReturningMetricStat := map[string]queryParameters{ + "with specific dimensions, matchExact true": { + Dimensions: queryDimensions{[]string{"some-instance"}}, + MatchExact: true, + MetricName: "CPUUtilization", + Statistic: "Maximum", + }, + "no dimensions, matchExact true": { + Dimensions: queryDimensions{}, + MatchExact: true, + MetricName: "CPUUtilization", + Statistic: "Maximum", + }, + "multivalued dimensions, matchExact true": { + Dimensions: queryDimensions{[]string{"some-instance", "another-instance"}}, + MatchExact: true, + MetricName: "CPUUtilization", + Statistic: "Maximum", + }, + "multivalued dimensions, matchExact false": { + Dimensions: queryDimensions{[]string{"some-instance", "another-instance"}}, + MatchExact: false, + MetricName: "CPUUtilization", + Statistic: "Maximum", + }, + } + for name, parameters := range testCasesReturningMetricStat { + t.Run(name, func(t *testing.T) { + query := newTestQuery(t, parameters) + + resp, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + RefID: "A", + TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)}, + JSON: query, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "CPUUtilization_Maximum", resp.Responses["A"].Frames[0].Name) + }) + } +} diff --git a/pkg/tsdb/loki/api.go b/pkg/tsdb/loki/api.go index 08e3b1eb8c5..d1d1eb6f813 100644 --- a/pkg/tsdb/loki/api.go +++ b/pkg/tsdb/loki/api.go @@ -25,10 +25,12 @@ func newLokiAPI(client *http.Client, url string, log log.Logger) *LokiAPI { return &LokiAPI{client: client, url: url, log: log} } -func makeRequest(ctx context.Context, lokiDsUrl string, query lokiQuery) (*http.Request, error) { +func makeDataRequest(ctx context.Context, lokiDsUrl string, query lokiQuery) (*http.Request, error) { qs := url.Values{} qs.Set("query", query.Expr) + qs.Set("direction", string(query.Direction)) + // MaxLines defaults to zero when not received, // and Loki does not like limit=0, even when it is not needed // (for example for metric queries), so we @@ -133,8 +135,8 @@ func makeLokiError(body io.ReadCloser) error { return fmt.Errorf("%v", errorMessage) } -func (api *LokiAPI) Query(ctx context.Context, query lokiQuery) (*loghttp.QueryResponse, error) { - req, err := makeRequest(ctx, api.url, query) +func (api *LokiAPI) DataQuery(ctx context.Context, query lokiQuery) (*loghttp.QueryResponse, error) { + req, err := makeDataRequest(ctx, api.url, query) if err != nil { return nil, err } @@ -162,3 +164,41 @@ func (api *LokiAPI) Query(ctx context.Context, query lokiQuery) (*loghttp.QueryR return &response, nil } + +func makeRawRequest(ctx context.Context, lokiDsUrl string, resourceURL string) (*http.Request, error) { + lokiUrl, err := url.Parse(lokiDsUrl) + if err != nil { + return nil, err + } + + url, err := lokiUrl.Parse(resourceURL) + if err != nil { + return nil, err + } + + return http.NewRequestWithContext(ctx, "GET", url.String(), nil) +} + +func (api *LokiAPI) RawQuery(ctx context.Context, resourceURL string) ([]byte, error) { + req, err := makeRawRequest(ctx, api.url, resourceURL) + if err != nil { + return nil, err + } + + resp, err := api.client.Do(req) + if err != nil { + return nil, err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + api.log.Warn("Failed to close response body", "err", err) + } + }() + + if resp.StatusCode/100 != 2 { + return nil, makeLokiError(resp.Body) + } + + return io.ReadAll(resp.Body) +} diff --git a/pkg/tsdb/loki/framing_test.go b/pkg/tsdb/loki/framing_test.go index 696cc5d3fb6..8525a7f0673 100644 --- a/pkg/tsdb/loki/framing_test.go +++ b/pkg/tsdb/loki/framing_test.go @@ -22,9 +22,9 @@ import ( // but i wanted to test for all of them, to be sure. func TestSuccessResponse(t *testing.T) { - matrixQuery := lokiQuery{Expr: "up(ALERTS)", Step: time.Second * 42, QueryType: QueryTypeRange} - vectorQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeInstant} - streamsQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeRange} + matrixQuery := lokiQuery{Expr: "up(ALERTS)", Step: time.Second * 42, QueryType: QueryTypeRange, Direction: DirectionBackward} + vectorQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeInstant, Direction: DirectionBackward} + streamsQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeRange, Direction: DirectionBackward} tt := []struct { name string @@ -119,7 +119,7 @@ func TestErrorResponse(t *testing.T) { for _, test := range tt { t.Run(test.name, func(t *testing.T) { - frames, err := runQuery(context.Background(), makeMockedAPI(400, test.contentType, test.body), &lokiQuery{QueryType: QueryTypeRange}) + frames, err := runQuery(context.Background(), makeMockedAPI(400, test.contentType, test.body), &lokiQuery{QueryType: QueryTypeRange, Direction: DirectionBackward}) require.Len(t, frames, 0) require.Error(t, err) diff --git a/pkg/tsdb/loki/loki.go b/pkg/tsdb/loki/loki.go index 59cc30eb7e1..976c1db464d 100644 --- a/pkg/tsdb/loki/loki.go +++ b/pkg/tsdb/loki/loki.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "regexp" + "strings" "sync" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -25,8 +26,9 @@ type Service struct { } var ( - _ backend.QueryDataHandler = (*Service)(nil) - _ backend.StreamHandler = (*Service)(nil) + _ backend.QueryDataHandler = (*Service)(nil) + _ backend.StreamHandler = (*Service)(nil) + _ backend.CallResourceHandler = (*Service)(nil) ) func ProvideService(httpClientProvider httpclient.Provider, tracer tracing.Tracer) *Service { @@ -53,6 +55,7 @@ type datasourceInfo struct { type QueryJSONModel struct { QueryType string `json:"queryType"` Expr string `json:"expr"` + Direction string `json:"direction"` LegendFormat string `json:"legendFormat"` Interval string `json:"interval"` IntervalMS int `json:"intervalMS"` @@ -88,6 +91,40 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst } } +func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { + url := req.URL + + // a very basic is-this-url-valid check + if req.Method != "GET" { + return fmt.Errorf("invalid resource method: %s", req.Method) + } + if (!strings.HasPrefix(url, "/loki/api/v1/label?")) && + (!strings.HasPrefix(url, "/loki/api/v1/label/")) && // the `/label/$label_name/values` form + (!strings.HasPrefix(url, "/loki/api/v1/series?")) { + return fmt.Errorf("invalid resource URL: %s", url) + } + + dsInfo, err := s.getDSInfo(req.PluginContext) + if err != nil { + return err + } + + api := newLokiAPI(dsInfo.HTTPClient, dsInfo.URL, s.plog) + bytes, err := api.RawQuery(ctx, url) + + if err != nil { + return err + } + + return sender.Send(&backend.CallResourceResponse{ + Status: http.StatusOK, + Headers: map[string][]string{ + "content-type": {"application/json"}, + }, + Body: bytes, + }) +} + func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { result := backend.NewQueryDataResponse() @@ -128,7 +165,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) // we extracted this part of the functionality to make it easy to unit-test it func runQuery(ctx context.Context, api *LokiAPI, query *lokiQuery) (data.Frames, error) { - value, err := api.Query(ctx, *query) + value, err := api.DataQuery(ctx, *query) if err != nil { return data.Frames{}, err } diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go index d62802e5f06..80f0c0cbc27 100644 --- a/pkg/tsdb/loki/parse_query.go +++ b/pkg/tsdb/loki/parse_query.go @@ -67,6 +67,21 @@ func parseQueryType(jsonValue string) (QueryType, error) { } } +func parseDirection(jsonValue string) (Direction, error) { + switch jsonValue { + case "backward": + return DirectionBackward, nil + case "forward": + return DirectionForward, nil + case "": + // there are older queries stored in alerting that did not have queryDirection, + // we default to "backward" + return DirectionBackward, nil + default: + return DirectionBackward, fmt.Errorf("invalid queryDirection: %s", jsonValue) + } +} + func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { qs := []*lokiQuery{} for _, query := range queryContext.Queries { @@ -95,9 +110,15 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { return nil, err } + direction, err := parseDirection(model.Direction) + if err != nil { + return nil, err + } + qs = append(qs, &lokiQuery{ Expr: expr, QueryType: queryType, + Direction: direction, Step: step, MaxLines: model.MaxLines, LegendFormat: model.LegendFormat, diff --git a/pkg/tsdb/loki/types.go b/pkg/tsdb/loki/types.go index 309d2ef8246..9d5fcead26d 100644 --- a/pkg/tsdb/loki/types.go +++ b/pkg/tsdb/loki/types.go @@ -9,9 +9,17 @@ const ( QueryTypeInstant QueryType = "instant" ) +type Direction string + +const ( + DirectionBackward Direction = "backward" + DirectionForward Direction = "forward" +) + type lokiQuery struct { Expr string QueryType QueryType + Direction Direction Step time.Duration MaxLines int LegendFormat string diff --git a/pkg/tsdb/testdatasource/flight_path.go b/pkg/tsdb/testdatasource/flight_path.go deleted file mode 100644 index 1176768f92f..00000000000 --- a/pkg/tsdb/testdatasource/flight_path.go +++ /dev/null @@ -1,135 +0,0 @@ -package testdatasource - -import ( - "context" - "fmt" - "math" - "time" - - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana/pkg/components/simplejson" -) - -func (s *Service) handleFlightPathScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { - resp := backend.NewQueryDataResponse() - - for _, q := range req.Queries { - _, err := simplejson.NewJson(q.JSON) - if err != nil { - return nil, fmt.Errorf("failed to parse query json: %v", err) - } - - timeWalkerMs := q.TimeRange.From.UnixNano() / int64(time.Millisecond) - to := q.TimeRange.To.UnixNano() / int64(time.Millisecond) - stepMillis := q.Interval.Milliseconds() - - cfg := newFlightConfig() - - maxPoints := q.MaxDataPoints * 2 - f := cfg.initFields() - for i := int64(0); i < maxPoints && timeWalkerMs < to; i++ { - t := time.Unix(timeWalkerMs/int64(1e+3), (timeWalkerMs%int64(1e+3))*int64(1e+6)).UTC() - f.append(cfg.getNextPoint(t)) - - timeWalkerMs += stepMillis - } - - // When close to now, link to the live streaming channel - if q.TimeRange.To.Add(time.Second * 2).After(time.Now()) { - f.frame.Meta = &data.FrameMeta{ - Channel: "plugin/testdata/flight-5hz-stream", - } - } - - respD := resp.Responses[q.RefID] - respD.Frames = append(respD.Frames, f.frame) - resp.Responses[q.RefID] = respD - } - - return resp, nil -} - -type flightDataPoint struct { - time time.Time - lat float64 // gps - lng float64 // gps - heading float64 // degree - altitude float64 // above ground -} - -type flightConfig struct { - centerLat float64 - centerLng float64 - radius float64 - altitudeMin float64 - altitudeMax float64 - periodS float64 // angular speed -} - -func newFlightConfig() *flightConfig { - return &flightConfig{ - centerLat: 37.83, // San francisco - centerLng: -122.42487, - radius: 0.01, // raw gps degrees - altitudeMin: 350, - altitudeMax: 400, - periodS: 10, //model.Get("period").MustFloat64(10), - } -} - -type flightFields struct { - time *data.Field - lat *data.Field - lng *data.Field - heading *data.Field - altitude *data.Field - - frame *data.Frame -} - -func (f *flightConfig) initFields() *flightFields { - ff := &flightFields{ - time: data.NewFieldFromFieldType(data.FieldTypeTime, 0), - lat: data.NewFieldFromFieldType(data.FieldTypeFloat64, 0), - lng: data.NewFieldFromFieldType(data.FieldTypeFloat64, 0), - heading: data.NewFieldFromFieldType(data.FieldTypeFloat64, 0), - altitude: data.NewFieldFromFieldType(data.FieldTypeFloat64, 0), - } - ff.time.Name = "time" - ff.lat.Name = "lat" - ff.lng.Name = "lng" - ff.heading.Name = "heading" - ff.altitude.Name = "altitude" - - ff.frame = data.NewFrame("", ff.time, ff.lat, ff.lng, ff.heading, ff.altitude) - return ff -} - -func (f *flightFields) append(v flightDataPoint) { - f.frame.AppendRow(v.time, v.lat, v.lng, v.heading, v.altitude) -} - -func (f *flightFields) set(idx int, v flightDataPoint) { - f.time.Set(idx, v.time) - f.lat.Set(idx, v.lat) - f.lng.Set(idx, v.lng) - f.heading.Set(idx, v.heading) - f.altitude.Set(idx, v.altitude) -} - -func (f *flightConfig) getNextPoint(t time.Time) flightDataPoint { - periodNS := int64(f.periodS * float64(time.Second)) - ms := t.UnixNano() % periodNS - per := float64(ms) / float64(periodNS) - rad := per * 2.0 * math.Pi // 0 >> 2Pi - delta := f.altitudeMax - f.altitudeMin - - return flightDataPoint{ - time: t, - lat: f.centerLat + math.Sin(rad)*f.radius, - lng: f.centerLng + math.Cos(rad)*f.radius, - heading: (rad * 180) / math.Pi, // (math.Atanh(rad) * 180.0) / math.Pi, // in degrees - altitude: f.altitudeMin + (delta * per), // clif - } -} diff --git a/pkg/tsdb/testdatasource/resource_handler.go b/pkg/tsdb/testdatasource/resource_handler.go index 76bbed47e42..8b64a2cfeb8 100644 --- a/pkg/tsdb/testdatasource/resource_handler.go +++ b/pkg/tsdb/testdatasource/resource_handler.go @@ -23,6 +23,8 @@ func (s *Service) registerRoutes() *http.ServeMux { mux.Handle("/test", createJSONHandler(s.logger)) mux.Handle("/test/json", createJSONHandler(s.logger)) mux.HandleFunc("/boom", s.testPanicHandler) + mux.HandleFunc("/sims", s.sims.GetSimulationHandler) + mux.HandleFunc("/sim/", s.sims.GetSimulationHandler) return mux } diff --git a/pkg/tsdb/testdatasource/scenarios.go b/pkg/tsdb/testdatasource/scenarios.go index e49d760cef2..c73ec20497f 100644 --- a/pkg/tsdb/testdatasource/scenarios.go +++ b/pkg/tsdb/testdatasource/scenarios.go @@ -30,7 +30,7 @@ const ( predictablePulseQuery queryType = "predictable_pulse" predictableCSVWaveQuery queryType = "predictable_csv_wave" streamingClientQuery queryType = "streaming_client" - flightPath queryType = "flight_path" + simulation queryType = "simulation" usaQueryKey queryType = "usa" liveQuery queryType = "live" grafanaAPIQuery queryType = "grafana_api" @@ -135,9 +135,9 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means }) s.registerScenario(&Scenario{ - ID: string(flightPath), - Name: "Flight path", - handler: s.handleFlightPathScenario, + ID: string(simulation), + Name: "Simulation", + handler: s.sims.QueryData, }) s.registerScenario(&Scenario{ diff --git a/pkg/tsdb/testdatasource/sims/engine.go b/pkg/tsdb/testdatasource/sims/engine.go new file mode 100644 index 00000000000..fa18df54bc6 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/engine.go @@ -0,0 +1,309 @@ +package sims + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/grafana/grafana/pkg/infra/log" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +var ( + _ backend.StreamHandler = (*SimulationEngine)(nil) + _ backend.QueryDataHandler = (*SimulationEngine)(nil) +) + +type SimulationEngine struct { + logger log.Logger + + // Lookup by Type + registry map[string]simulationInfo + + // The running instances + running map[string]Simulation + + // safe changes + mutex sync.Mutex +} + +func (s *SimulationEngine) register(info simulationInfo) error { + if info.create == nil { + return fmt.Errorf("invalid simulation -- missing create function: " + info.Type) + } + if info.Type == "" { + return fmt.Errorf("missing type") + } + + s.mutex.Lock() + defer s.mutex.Unlock() + + _, ok := s.registry[info.Type] + if ok { + return fmt.Errorf("already registered") + } + + s.registry[info.Type] = info + return nil +} + +type simulationInitializer = func() simulationInfo + +func NewSimulationEngine() (*SimulationEngine, error) { + s := &SimulationEngine{ + registry: make(map[string]simulationInfo), + running: make(map[string]Simulation), + logger: log.New("tsdb.sims"), + } + // Initialize each type + initializers := []simulationInitializer{ + newFlightSimInfo, + newSinewaveInfo, + newTankSimInfo, + } + + for _, init := range initializers { + err := s.register(init()) + if err != nil { + return s, err + } + } + return s, nil +} + +func (s *SimulationEngine) Lookup(info simulationState) (Simulation, error) { + hz := info.Key.TickHZ + if hz < (1 / 60.0) { + return nil, fmt.Errorf("frequency is too slow") + } + if hz > 50 { + return nil, fmt.Errorf("frequency is too fast") + } + if info.Key.Type == "" { + return nil, fmt.Errorf("missing simulation type") + } + + key := info.Key.String() + s.mutex.Lock() + defer s.mutex.Unlock() + + v, ok := s.running[key] + if ok { + return v, nil + } + + t, ok := s.registry[info.Key.Type] + if !ok { + return nil, fmt.Errorf("unknown simulation type") + } + + v, err := t.create(info) + if err == nil { + s.running[key] = v + } + return v, err +} + +type simulationQuery struct { + simulationState + Last bool `json:"last"` + Stream bool `json:"stream"` +} + +type dumbQueryQrapper struct { + Sim simulationQuery `json:"sim"` +} + +func (s *SimulationEngine) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + resp := backend.NewQueryDataResponse() + + for _, q := range req.Queries { + wrap := &dumbQueryQrapper{} + err := json.Unmarshal(q.JSON, wrap) + if err != nil { + return nil, fmt.Errorf("failed to parse query json: %v", err) + } + sq := wrap.Sim + if sq.Key.TickHZ == 0 { + sq.Key.TickHZ = 10 + } + + sim, err := s.Lookup(sq.simulationState) + if err != nil { + return nil, fmt.Errorf("error fetching simulation: %v", err) + } + + if sim == nil { + return nil, fmt.Errorf("invalid simulation: %v", sq) + } + + frame := sim.NewFrame(0) + if sq.Last { + v := sim.GetValues(q.TimeRange.To) + appendFrameRow(frame, v) + } else { + timeWalkerMs := q.TimeRange.From.UnixNano() / int64(time.Millisecond) + to := q.TimeRange.To.UnixNano() / int64(time.Millisecond) + stepMillis := q.Interval.Milliseconds() + + maxPoints := q.MaxDataPoints * 2 + for i := int64(0); i < maxPoints && timeWalkerMs < to; i++ { + t := time.UnixMilli(timeWalkerMs).UTC() + vals := sim.GetValues(t) + if vals != nil { // nil is returned when you ask for an invalid time + appendFrameRow(frame, vals) + } + timeWalkerMs += stepMillis + } + } + + if sq.Stream && req.PluginContext.DataSourceInstanceSettings != nil { + uid := req.PluginContext.DataSourceInstanceSettings.UID + frame.Meta = &data.FrameMeta{ + Channel: fmt.Sprintf("ds/%s/sim/%s", uid, sim.GetState().Key.String()), + } + } + + respD := resp.Responses[q.RefID] + respD.Frames = append(respD.Frames, frame) + resp.Responses[q.RefID] = respD + } + + return resp, nil +} + +func (s *SimulationEngine) getSimFromPath(path string) (Simulation, error) { + idx := strings.Index(path, "sim/") + if idx >= 0 { + path = path[idx+4:] + } + + parts := strings.Split(path, "/") + if len(parts) < 2 { + return nil, fmt.Errorf("missing frequency") + } + if !strings.HasSuffix(parts[1], "hz") { + return nil, fmt.Errorf("invalid path frequency. Expecting `hz` suffix") + } + hz, err := strconv.ParseFloat(strings.TrimSuffix(parts[1], "hz"), 64) + if err != nil { + return nil, fmt.Errorf("error parsing frequency from: %s", parts[1]) + } + if len(parts) == 2 { + parts = append(parts, "") // empty UID + } + + key := simulationKey{ + Type: parts[0], + TickHZ: hz, + UID: parts[2], + } + if path != key.String() { + return nil, fmt.Errorf("path should match: %s", key.String()) + } + + return s.Lookup(simulationState{ + Key: key, + }) +} + +func (s *SimulationEngine) GetSimulationHandler(rw http.ResponseWriter, req *http.Request) { + var result interface{} + path := req.URL.Path + if path == "/sims" { + v := make([]simulationInfo, 0, len(s.registry)) + for _, value := range s.registry { + v = append(v, value) + } + result = v + } else if strings.HasPrefix(path, "/sim/") { + rw.WriteHeader(400) + sim, err := s.getSimFromPath(path) + if err != nil { + http.Error(rw, err.Error(), http.StatusNotFound) + return + } + + // With a POST, update the values + if req.Method == "POST" { + body, err := getBodyFromRequest(req) + if err == nil { + err = sim.SetConfig(body) + } + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + result = sim.GetState() + } + + bytes, err := json.Marshal(&result) + if err != nil { + s.logger.Error("Failed to marshal response body to JSON", "error", err) + } + + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(http.StatusOK) + if _, err := rw.Write(bytes); err != nil { + s.logger.Error("Failed to write response", "error", err) + } +} + +func (s *SimulationEngine) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { + sim, err := s.getSimFromPath(req.Path) // includes sim + if err != nil { + return nil, err + } + + frame := sim.NewFrame(1) + setFrameRow(frame, 0, sim.GetValues(time.Now())) + initial, err := backend.NewInitialFrame(frame, data.IncludeAll) + + return &backend.SubscribeStreamResponse{ + Status: backend.SubscribeStreamStatusOK, + InitialData: initial, + }, err +} + +func (s *SimulationEngine) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error { + sim, err := s.getSimFromPath(req.Path) // includes sim + if err != nil { + return err + } + + frequency := 1.0 / sim.GetState().Key.TickHZ + ticker := time.NewTicker(time.Duration(frequency * float64(time.Second))) + defer ticker.Stop() + + mode := data.IncludeDataOnly + + frame := sim.NewFrame(1) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + + case t := <-ticker.C: + setFrameRow(frame, 0, sim.GetValues(t)) + err := sender.SendFrame(frame, mode) + if err != nil { + return err + } + } + } +} + +func (s *SimulationEngine) PublishStream(_ context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) { + return &backend.PublishStreamResponse{ + Status: backend.PublishStreamStatusPermissionDenied, + }, nil +} diff --git a/pkg/tsdb/testdatasource/sims/engine_test.go b/pkg/tsdb/testdatasource/sims/engine_test.go new file mode 100644 index 00000000000..3f406d46304 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/engine_test.go @@ -0,0 +1,64 @@ +package sims + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCoreSimulationRegistry(t *testing.T) { + sims, err := NewSimulationEngine() + require.NoError(t, err) + v, err := sims.Lookup(simulationState{ + Key: simulationKey{ + Type: "flight", + TickHZ: 1, + }, + Config: map[string]interface{}{ + "period": 100, + "radius": 0.05, + }, + }) + require.NoError(t, err) + + cfg, err := json.MarshalIndent(v.GetState(), "", " ") + require.NoError(t, err) + require.JSONEq(t, `{ + "key": { + "type": "flight", + "tick": 1 + }, + "config": { + "centerLat":37.83, + "centerLng":-122.42487, + "altitudeMax":400, + "altitudeMin":350, + "period":100, + "radius":0.05 + } + }`, string(cfg)) + + path := v.GetState().Key.String() + found, err := sims.getSimFromPath("sim/" + path) + require.NoError(t, err) + require.Equal(t, v, found) + + found, err = sims.getSimFromPath("/sim/" + path) + require.NoError(t, err) + require.Equal(t, v, found) + + found, err = sims.getSimFromPath(path) + require.NoError(t, err) + require.Equal(t, v, found) + + // In valid paths + _, err = sims.getSimFromPath("flight/1.00hz") + require.Error(t, err) + + _, err = sims.getSimFromPath("flight/1") + require.Error(t, err) + + _, err = sims.getSimFromPath("flight/1/") + require.Error(t, err) +} diff --git a/pkg/tsdb/testdatasource/sims/flight_path.go b/pkg/tsdb/testdatasource/sims/flight_path.go new file mode 100644 index 00000000000..0bc00fc5395 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/flight_path.go @@ -0,0 +1,131 @@ +package sims + +import ( + "math" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type flightSim struct { + key simulationKey + cfg flightConfig +} + +var ( + _ Simulation = (*flightSim)(nil) +) + +type flightDataPoint struct { + time time.Time + lat float64 // gps + lng float64 // gps + heading float64 // degree + altitude float64 // above ground +} + +type flightConfig struct { + CenterLat float64 `json:"centerLat"` + CenterLng float64 `json:"centerLng"` + Radius float64 `json:"radius"` + AltitudeMin float64 `json:"altitudeMin"` + AltitudeMax float64 `json:"altitudeMax"` + Period float64 `json:"period"` // angular speed (seconds) +} + +func (s *flightSim) GetState() simulationState { + return simulationState{ + Key: s.key, + Config: s.cfg, + } +} + +func (s *flightSim) SetConfig(vals map[string]interface{}) error { + return updateConfigObjectFromJSON(s.cfg, vals) +} + +func (s *flightSim) NewFrame(size int) *data.Frame { + frame := data.NewFrameOfFieldTypes("", size, + data.FieldTypeTime, // time + data.FieldTypeFloat64, // lat + data.FieldTypeFloat64, // lng + data.FieldTypeFloat64, // heading + data.FieldTypeFloat64, // altitude + ) + frame.Fields[0].Name = "time" + frame.Fields[1].Name = "lat" + frame.Fields[2].Name = "lng" + frame.Fields[3].Name = "heading" + frame.Fields[4].Name = "altitude" + return frame +} + +func (s *flightSim) GetValues(t time.Time) map[string]interface{} { + p := s.cfg.getNextPoint(t) + + return map[string]interface{}{ + "time": p.time, + "lat": p.lat, + "lng": p.lng, + "heading": p.heading, + "altitude": p.altitude, + } +} + +func (s *flightSim) Close() error { + return nil +} + +func newFlightSimInfo() simulationInfo { + sf := flightConfig{ + CenterLat: 37.83, // San francisco + CenterLng: -122.42487, + Radius: 0.01, // raw gps degrees + AltitudeMin: 350, + AltitudeMax: 400, + Period: 10, //model.Get("period").MustFloat64(10), + } + + df := data.NewFrame("") + df.Fields = append(df.Fields, data.NewField("centerLat", nil, []float64{sf.CenterLat})) + df.Fields = append(df.Fields, data.NewField("centerLng", nil, []float64{sf.CenterLng})) + df.Fields = append(df.Fields, data.NewField("radius", nil, []float64{sf.Radius})) + df.Fields = append(df.Fields, data.NewField("altitudeMin", nil, []float64{sf.AltitudeMin})) + df.Fields = append(df.Fields, data.NewField("altitudeMax", nil, []float64{sf.AltitudeMax})) + + f := data.NewField("period", nil, []float64{sf.Period}) + f.Config = &data.FieldConfig{Unit: "s"} // seconds + df.Fields = append(df.Fields, f) + + return simulationInfo{ + Type: "flight", + Name: "Flight", + Description: "simple circling airplain", + SetupFields: df, + OnlyForward: false, + create: func(cfg simulationState) (Simulation, error) { + s := &flightSim{ + key: cfg.Key, + cfg: sf, // default value + } + err := updateConfigObjectFromJSON(&s.cfg, cfg.Config) // override any fields + return s, err + }, + } +} + +func (f *flightConfig) getNextPoint(t time.Time) flightDataPoint { + periodNS := int64(f.Period * float64(time.Second)) + ms := t.UnixNano() % periodNS + per := float64(ms) / float64(periodNS) + rad := per * 2.0 * math.Pi // 0 >> 2Pi + delta := f.AltitudeMax - f.AltitudeMin + + return flightDataPoint{ + time: t, + lat: f.CenterLat + math.Sin(rad)*f.Radius, + lng: f.CenterLng + math.Cos(rad)*f.Radius, + heading: (rad * 180) / math.Pi, // (math.Atanh(rad) * 180.0) / math.Pi, // in degrees + altitude: f.AltitudeMin + (delta * per), // clif + } +} diff --git a/pkg/tsdb/testdatasource/flight_path_test.go b/pkg/tsdb/testdatasource/sims/flight_path_test.go similarity index 61% rename from pkg/tsdb/testdatasource/flight_path_test.go rename to pkg/tsdb/testdatasource/sims/flight_path_test.go index 24ca8a80755..b62a68e31e6 100644 --- a/pkg/tsdb/testdatasource/flight_path_test.go +++ b/pkg/tsdb/testdatasource/sims/flight_path_test.go @@ -1,4 +1,4 @@ -package testdatasource +package sims import ( "context" @@ -10,39 +10,47 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/experimental" - "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" ) -func TestFlightPathScenario(t *testing.T) { - cfg := setting.NewCfg() - s := &Service{ - cfg: cfg, - } +func TestFlightPathQuery(t *testing.T) { + s, err := NewSimulationEngine() + require.NoError(t, err) t.Run("simple flight", func(t *testing.T) { + sq := &simulationQuery{} + sq.Key = simulationKey{ + Type: "flight", + TickHZ: 1, + } + sq.Stream = true + sb, err := json.Marshal(map[string]interface{}{ + "sim": sq, + }) + require.NoError(t, err) + start := time.Date(2020, time.January, 10, 23, 0, 0, 0, time.UTC) qr := &backend.QueryDataRequest{ Queries: []backend.DataQuery{ { - RefID: "X", + RefID: "A", TimeRange: backend.TimeRange{ From: start, To: start.Add(time.Second * 10), }, Interval: time.Second, MaxDataPoints: 10, - JSON: json.RawMessage(`{}`), // always 10s? + JSON: sb, }, }, } - rsp, err := s.handleFlightPathScenario(context.Background(), qr) + rsp, err := s.QueryData(context.Background(), qr) require.NoError(t, err) require.NotNil(t, rsp) for k, v := range rsp.Responses { dr := v - filePath := filepath.Join("testdata", fmt.Sprintf("flight-simple-%s.txt", k)) + filePath := filepath.Join("testdata", fmt.Sprintf("flight_path_query_%s.txt", k)) err = experimental.CheckGoldenDataResponse(filePath, &dr, true) require.NoError(t, err) } diff --git a/pkg/tsdb/testdatasource/sims/tank.go b/pkg/tsdb/testdatasource/sims/tank.go new file mode 100644 index 00000000000..ce8c1626374 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/tank.go @@ -0,0 +1,152 @@ +package sims + +import ( + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type tankSim struct { + key simulationKey + cfg tankConfig + state tankState +} + +var ( + _ Simulation = (*tankSim)(nil) +) + +type tankConfig struct { + TankCapacity float64 `json:"tankCapacity"` // size (in gallons) of the tank + FillRate float64 `json:"fillRate"` // fillRate + DrainRate float64 `json:"drainRate"` // how fast it will drain (when valve is open) + DrainOpen bool `json:"drainOpen"` + PumpOn bool `json:"pumpOn"` +} + +type tankState struct { + Time time.Time + FillPercent float64 // 0-1 +} + +func (s *tankSim) GetState() simulationState { + return simulationState{ + Key: s.key, + Config: s.cfg, + } +} + +func (s *tankSim) SetConfig(vals map[string]interface{}) error { + return updateConfigObjectFromJSON(&s.cfg, vals) +} + +func (s *tankSim) NewFrame(size int) *data.Frame { + frame := data.NewFrameOfFieldTypes("", size, + data.FieldTypeTime, // time + data.FieldTypeFloat64, // value + data.FieldTypeFloat64, // value + data.FieldTypeBool, + data.FieldTypeBool, + ) + + var min data.ConfFloat64 = 0.0 + var max data.ConfFloat64 = 1.0 + + frame.Fields[0].Name = "time" + frame.Fields[1].Name = "percentFull" + frame.Fields[1].Config = &data.FieldConfig{ + Unit: "percentunit", + Min: &min, + Max: &max, + } + + frame.Fields[2].Name = "tankVolume" + frame.Fields[2].Config = &data.FieldConfig{ + Unit: "m3", // cubic meters + } + frame.Fields[3].Name = "pumpOn" + frame.Fields[4].Name = "drainOpen" + return frame +} + +func (s *tankSim) GetValues(t time.Time) map[string]interface{} { + if t.Before(s.state.Time) { + return nil // can not look backwards! + } + if t.After(s.state.Time) { + elapsed := t.Sub(s.state.Time).Seconds() + v := s.state.FillPercent * s.cfg.TankCapacity + if s.cfg.PumpOn { + v += (elapsed * s.cfg.FillRate) + } + if s.cfg.DrainOpen { + v -= (elapsed * s.cfg.DrainRate) + } + + p := v / s.cfg.TankCapacity + if p > 1 { + p = 1 + } else if p < 0 { + p = 0 + } + + s.state.FillPercent = p + s.state.Time = t + } + + return map[string]interface{}{ + "time": s.state.Time, + "percentFull": s.state.FillPercent, + "tankVolume": s.state.FillPercent * s.cfg.TankCapacity, + "pumpOn": s.cfg.PumpOn, + "drainOpen": s.cfg.DrainOpen, + } +} + +func (s *tankSim) Close() error { + return nil +} + +func newTankSimInfo() simulationInfo { + tc := tankConfig{ + TankCapacity: 100.0, + FillRate: 1.0, // gallons/min? + DrainRate: 0.5, // `json:"drainRate,omitempty"` // how fast it will drain (when valve is open) + DrainOpen: false, + PumpOn: true, + } + ts := tankState{ + Time: time.Now(), + FillPercent: .6, + } + + df := data.NewFrame("") + df.Fields = append(df.Fields, data.NewField("tankCapacity", nil, []float64{tc.TankCapacity}).SetConfig(&data.FieldConfig{ + Unit: "m3", + })) + df.Fields = append(df.Fields, data.NewField("fillRate", nil, []float64{tc.FillRate}).SetConfig(&data.FieldConfig{ + Unit: "cms", + })) + df.Fields = append(df.Fields, data.NewField("drainRate", nil, []float64{tc.DrainRate}).SetConfig(&data.FieldConfig{ + Unit: "cms", + })) + df.Fields = append(df.Fields, data.NewField("drainOpen", nil, []bool{tc.DrainOpen})) + df.Fields = append(df.Fields, data.NewField("pumpOn", nil, []bool{tc.PumpOn})) + + return simulationInfo{ + Type: "tank", + Name: "Tank", + Description: "Fill and drain a water tank", + SetupFields: df, + OnlyForward: false, + create: func(cfg simulationState) (Simulation, error) { + s := &tankSim{ + key: cfg.Key, + cfg: tc, + state: ts, + } + err := updateConfigObjectFromJSON(&s.cfg, cfg.Config) // override any fields + return s, err + }, + } +} diff --git a/pkg/tsdb/testdatasource/testdata/flight-simple-X.txt b/pkg/tsdb/testdatasource/sims/testdata/flight_path_query_A.txt similarity index 95% rename from pkg/tsdb/testdatasource/testdata/flight-simple-X.txt rename to pkg/tsdb/testdatasource/sims/testdata/flight_path_query_A.txt index eef49197339..a7ce0755c65 100644 --- a/pkg/tsdb/testdatasource/testdata/flight-simple-X.txt +++ b/pkg/tsdb/testdatasource/sims/testdata/flight_path_query_A.txt @@ -1,7 +1,7 @@ 🌟 This was machine generated. Do not edit. 🌟 -Frame[0] -Name: +Frame[0] +Name: Dimensions: 5 Fields by 10 Rows +-------------------------------+--------------------+---------------------+-----------------+-----------------+ | Name: time | Name: lat | Name: lng | Name: heading | Name: altitude | @@ -22,4 +22,4 @@ Dimensions: 5 Fields by 10 Rows ====== TEST DATA RESPONSE (arrow base64) ====== -FRAME=QVJST1cxAAD/////gAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAAM/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAACz+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAABQAAAJABAAAkAQAAzAAAAGwAAAAEAAAAlv7//xQAAABAAAAAQAAAAAAAAANAAAAAAQAAAAQAAACE/v//CAAAABQAAAAIAAAAYWx0aXR1ZGUAAAAABAAAAG5hbWUAAAAAAAAAAH7+//8AAAIACAAAAGFsdGl0dWRlAAAAAPr+//8UAAAAPAAAADwAAAAAAAADPAAAAAEAAAAEAAAA6P7//wgAAAAQAAAABwAAAGhlYWRpbmcABAAAAG5hbWUAAAAAAAAAAN7+//8AAAIABwAAAGhlYWRpbmcAVv///xQAAAA4AAAAOAAAAAAAAAM4AAAAAQAAAAQAAABE////CAAAAAwAAAADAAAAbG5nAAQAAABuYW1lAAAAAAAAAAA2////AAACAAMAAABsbmcAqv///xQAAAA4AAAAOAAAAAAAAAM4AAAAAQAAAAQAAACY////CAAAAAwAAAADAAAAbGF0AAQAAABuYW1lAAAAAAAAAACK////AAACAAMAAABsYXQAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAACkwAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABAAAAHRpbWUAAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAdGltZQAAAAD/////SAEAABQAAAAAAAAADAAWABQAEwAMAAQADAAAAJABAAAAAAAAFAAAAAAAAAMDAAoAGAAMAAgABAAKAAAAFAAAALgAAAAKAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAFAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAUAAAAAAAAABAAQAAAAAAAAAAAAAAAAAAQAEAAAAAAABQAAAAAAAAAAAAAAAFAAAACgAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAYOc1vajoFQAqgnG9qOgVAPQcrb2o6BUAvrfovajoFQCIUiS+qOgVAFLtX76o6BUAHIibvqjoFQDmIte+qOgVALC9Er+o6BUAelhOv6joFQrXo3A96kJAsQSkC/7qQkC7AgsVdetCQLsCCxV160JAsQSkC/7qQkAK16NwPepCQGOpo9V86UJAWas8zAXpQkBZqzzMBelCQGOpo9V86UJA24XmOo2aXsBW7k6FrJpewA8N1HD+ml7AiXkNs2ObXsBCmJKetZtewL0A++jUm17AQpiSnrWbXsCJeQ2zY5tewA8N1HD+ml7AVu5OhayaXsAAAAAAAAAAAAAAAAAAAEJAAAAAAAAAUkAAAAAAAABbQAAAAAAAAGJAAAAAAACAZkAAAAAAAABrQAAAAAAAgG9AAAAAAAAAckAAAAAAAEB0QAAAAAAA4HVAAAAAAAAwdkAAAAAAAIB2QAAAAAAA0HZAAAAAAAAgd0AAAAAAAHB3QAAAAAAAwHdAAAAAAAAQeEAAAAAAAGB4QAAAAAAAsHhAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADwAAAAAAAMAAQAAAJACAAAAAAAAUAEAAAAAAACQAQAAAAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAAz+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAALP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAAFAAAAkAEAACQBAADMAAAAbAAAAAQAAACW/v//FAAAAEAAAABAAAAAAAAAA0AAAAABAAAABAAAAIT+//8IAAAAFAAAAAgAAABhbHRpdHVkZQAAAAAEAAAAbmFtZQAAAAAAAAAAfv7//wAAAgAIAAAAYWx0aXR1ZGUAAAAA+v7//xQAAAA8AAAAPAAAAAAAAAM8AAAAAQAAAAQAAADo/v//CAAAABAAAAAHAAAAaGVhZGluZwAEAAAAbmFtZQAAAAAAAAAA3v7//wAAAgAHAAAAaGVhZGluZwBW////FAAAADgAAAA4AAAAAAAAAzgAAAABAAAABAAAAET///8IAAAADAAAAAMAAABsbmcABAAAAG5hbWUAAAAAAAAAADb///8AAAIAAwAAAGxuZwCq////FAAAADgAAAA4AAAAAAAAAzgAAAABAAAABAAAAJj///8IAAAADAAAAAMAAABsYXQABAAAAG5hbWUAAAAAAAAAAIr///8AAAIAAwAAAGxhdAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAAKTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAEAAAAdGltZQAAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAQAAAB0aW1lAAAAALACAABBUlJPVzE= +FRAME=QVJST1cxAAD/////gAIAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAAAM/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAACz+//8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAABQAAAJABAAAkAQAAzAAAAGwAAAAEAAAAlv7//xQAAABAAAAAQAAAAAAAAANAAAAAAQAAAAQAAACE/v//CAAAABQAAAAIAAAAYWx0aXR1ZGUAAAAABAAAAG5hbWUAAAAAAAAAAH7+//8AAAIACAAAAGFsdGl0dWRlAAAAAPr+//8UAAAAPAAAADwAAAAAAAADPAAAAAEAAAAEAAAA6P7//wgAAAAQAAAABwAAAGhlYWRpbmcABAAAAG5hbWUAAAAAAAAAAN7+//8AAAIABwAAAGhlYWRpbmcAVv///xQAAAA4AAAAOAAAAAAAAAM4AAAAAQAAAAQAAABE////CAAAAAwAAAADAAAAbG5nAAQAAABuYW1lAAAAAAAAAAA2////AAACAAMAAABsbmcAqv///xQAAAA4AAAAOAAAAAAAAAM4AAAAAQAAAAQAAACY////CAAAAAwAAAADAAAAbGF0AAQAAABuYW1lAAAAAAAAAACK////AAACAAMAAABsYXQAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAACkwAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABAAAAHRpbWUAAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAEAAAAdGltZQAAAAD/////SAEAABQAAAAAAAAADAAWABQAEwAMAAQADAAAAJABAAAAAAAAFAAAAAAAAAMEAAoAGAAMAAgABAAKAAAAFAAAALgAAAAKAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAFAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAUAAAAAAAAABAAQAAAAAAAAAAAAAAAAAAQAEAAAAAAABQAAAAAAAAAAAAAAAFAAAACgAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAYOc1vajoFQAqgnG9qOgVAPQcrb2o6BUAvrfovajoFQCIUiS+qOgVAFLtX76o6BUAHIibvqjoFQDmIte+qOgVALC9Er+o6BUAelhOv6joFQrXo3A96kJAsQSkC/7qQkC7AgsVdetCQLsCCxV160JAsQSkC/7qQkAK16NwPepCQGOpo9V86UJAWas8zAXpQkBZqzzMBelCQGOpo9V86UJA24XmOo2aXsBW7k6FrJpewA8N1HD+ml7AiXkNs2ObXsBCmJKetZtewL0A++jUm17AQpiSnrWbXsCJeQ2zY5tewA8N1HD+ml7AVu5OhayaXsAAAAAAAAAAAAAAAAAAAEJAAAAAAAAAUkAAAAAAAABbQAAAAAAAAGJAAAAAAACAZkAAAAAAAABrQAAAAAAAgG9AAAAAAAAAckAAAAAAAEB0QAAAAAAA4HVAAAAAAAAwdkAAAAAAAIB2QAAAAAAA0HZAAAAAAAAgd0AAAAAAAHB3QAAAAAAAwHdAAAAAAAAQeEAAAAAAAGB4QAAAAAAAsHhAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADwAAAAAAAQAAQAAAJACAAAAAAAAUAEAAAAAAACQAQAAAAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAUAAAAAIAAAAoAAAABAAAAAz+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAALP7//wgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAAFAAAAkAEAACQBAADMAAAAbAAAAAQAAACW/v//FAAAAEAAAABAAAAAAAAAA0AAAAABAAAABAAAAIT+//8IAAAAFAAAAAgAAABhbHRpdHVkZQAAAAAEAAAAbmFtZQAAAAAAAAAAfv7//wAAAgAIAAAAYWx0aXR1ZGUAAAAA+v7//xQAAAA8AAAAPAAAAAAAAAM8AAAAAQAAAAQAAADo/v//CAAAABAAAAAHAAAAaGVhZGluZwAEAAAAbmFtZQAAAAAAAAAA3v7//wAAAgAHAAAAaGVhZGluZwBW////FAAAADgAAAA4AAAAAAAAAzgAAAABAAAABAAAAET///8IAAAADAAAAAMAAABsbmcABAAAAG5hbWUAAAAAAAAAADb///8AAAIAAwAAAGxuZwCq////FAAAADgAAAA4AAAAAAAAAzgAAAABAAAABAAAAJj///8IAAAADAAAAAMAAABsYXQABAAAAG5hbWUAAAAAAAAAAIr///8AAAIAAwAAAGxhdAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAAKTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAEAAAAdGltZQAAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAQAAAB0aW1lAAAAALACAABBUlJPVzE= diff --git a/pkg/tsdb/testdatasource/sims/types.go b/pkg/tsdb/testdatasource/sims/types.go new file mode 100644 index 00000000000..42b9e7735d7 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/types.go @@ -0,0 +1,51 @@ +package sims + +import ( + "fmt" + "io" + "strings" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type simulationKey struct { + Type string `json:"type"` + TickHZ float64 `json:"tick"` // events/second + UID string `json:"uid,omitempty"` // allows many instances of the same type +} + +func (k simulationKey) String() string { + hz := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", k.TickHZ), "0"), ".") + if k.UID != "" { + return fmt.Sprintf("%s/%shz/%s", k.Type, hz, k.UID) + } + return fmt.Sprintf("%s/%shz", k.Type, hz) +} + +type simulationState struct { + // Identify the simulation instance + Key simulationKey `json:"key"` + + // Saved in panel options, and used to set initial values + Config interface{} `json:"config"` +} + +type simulationInfo struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + OnlyForward bool `json:"forward"` + SetupFields *data.Frame `json:"setup"` // the default setup fields (and types) + + // Create a simulation instance + create func(q simulationState) (Simulation, error) +} + +type Simulation interface { + io.Closer + GetState() simulationState + SetConfig(vals map[string]interface{}) error + NewFrame(size int) *data.Frame + GetValues(t time.Time) map[string]interface{} +} diff --git a/pkg/tsdb/testdatasource/sims/utils.go b/pkg/tsdb/testdatasource/sims/utils.go new file mode 100644 index 00000000000..39a9c8e43c7 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/utils.go @@ -0,0 +1,76 @@ +package sims + +import ( + "encoding/json" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// updateConfigObjectFromJSON will use json serialization to update any properties +func updateConfigObjectFromJSON(cfg interface{}, input interface{}) error { + current, err := asStringMap(cfg) + if err != nil { + return err + } + next, err := asStringMap(input) + if err != nil { + return err + } + + for k, v := range next { + if v == nil { + delete(current, k) + } else { + current[k] = v + } + } + + b, err := json.Marshal(current) + if err != nil { + return err + } + return json.Unmarshal(b, cfg) +} + +func asStringMap(input interface{}) (map[string]interface{}, error) { + v, ok := input.(map[string]interface{}) + if ok { + return v, nil + } + v = make(map[string]interface{}) + b, err := json.Marshal(input) + if err != nil { + return nil, err + } + err = json.Unmarshal(b, &v) + return v, err +} + +func setFrameRow(frame *data.Frame, idx int, values map[string]interface{}) { + for _, field := range frame.Fields { + v, ok := values[field.Name] + if ok { + field.Set(idx, v) + } + } +} + +func appendFrameRow(frame *data.Frame, values map[string]interface{}) { + for _, field := range frame.Fields { + v, ok := values[field.Name] + if ok { + field.Append(v) + } else { + field.Extend(1) // fill with nullable value + } + } +} + +func getBodyFromRequest(req *http.Request) (map[string]interface{}, error) { + result := make(map[string]interface{}, 10) + + err := json.NewDecoder(req.Body).Decode(&result) + // TODO? create the map based on form parameters not JSON post + return result, err +} diff --git a/pkg/tsdb/testdatasource/sims/utils_test.go b/pkg/tsdb/testdatasource/sims/utils_test.go new file mode 100644 index 00000000000..f38b4c423d3 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/utils_test.go @@ -0,0 +1,30 @@ +package sims + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSimulationUtils(t *testing.T) { + a := map[string]interface{}{ + "hello": "world", + "bool": true, + "number": 10, + } + + err := updateConfigObjectFromJSON(&a, map[string]interface{}{ + "bool": false, + "number": 5, + }) + require.NoError(t, err) + + cfg, err := json.MarshalIndent(a, "", " ") + require.NoError(t, err) + require.JSONEq(t, `{ + "hello": "world", + "bool": false, + "number": 5 + }`, string(cfg)) +} diff --git a/pkg/tsdb/testdatasource/sims/waveform.go b/pkg/tsdb/testdatasource/sims/waveform.go new file mode 100644 index 00000000000..6f78dd4b2a9 --- /dev/null +++ b/pkg/tsdb/testdatasource/sims/waveform.go @@ -0,0 +1,115 @@ +package sims + +import ( + "math" + "math/rand" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type waveformSim struct { + key simulationKey + cfg waveformConfig + calculator func(x float64, cfg *waveformConfig) float64 +} + +var ( + _ Simulation = (*waveformSim)(nil) +) + +type waveformConfig struct { + Period float64 `json:"period"` // seconds + Offset float64 `json:"offset,omitempty"` // Y shift + Phase float64 `json:"phase,omitempty"` // X shift // +- 1 (will scale the ) + Amplitude float64 `json:"amplitude"` // Y size + Noise float64 `json:"noise,omitempty"` // random noise to add +} + +func (s *waveformSim) GetState() simulationState { + return simulationState{ + Key: s.key, + Config: s.cfg, + } +} + +func (s *waveformSim) SetConfig(vals map[string]interface{}) error { + return updateConfigObjectFromJSON(s.cfg, vals) +} + +func (s *waveformSim) NewFrame(size int) *data.Frame { + frame := data.NewFrameOfFieldTypes("", size, + data.FieldTypeTime, // time + data.FieldTypeFloat64, // value + ) + frame.Fields[0].Name = data.TimeSeriesTimeFieldName + frame.Fields[1].Name = data.TimeSeriesValueFieldName + return frame +} + +func (s *waveformSim) GetValues(t time.Time) map[string]interface{} { + x := 0.0 + if s.cfg.Period > 0 { + periodMS := s.cfg.Period * 1000 + ms := t.UnixMilli() % int64(periodMS) + x = ((float64(ms) / periodMS) * 2 * math.Pi) // 0 >> 2Pi + } + + v := s.calculator(x, &s.cfg) + + noise := s.cfg.Noise + if noise > 0 { + gen := rand.New(rand.NewSource(t.UnixMilli())) // consistent for the value + v += (gen.Float64() * 2.0 * noise) - noise + } + + return map[string]interface{}{ + data.TimeSeriesTimeFieldName: t, + data.TimeSeriesValueFieldName: v, + } +} + +func (s *waveformSim) Close() error { + return nil +} + +func newSinewaveInfo() simulationInfo { + sf := waveformConfig{ + Period: 10, + Amplitude: 1, + Offset: 0, + Phase: 0, + } + + df := data.NewFrame("") + df.Fields = append(df.Fields, data.NewField("period", nil, []float64{sf.Period})) + df.Fields = append(df.Fields, data.NewField("offset", nil, []float64{sf.Offset})) + df.Fields = append(df.Fields, data.NewField("phase", nil, []float64{sf.Phase})) + df.Fields = append(df.Fields, data.NewField("amplitude", nil, []float64{sf.Amplitude})) + df.Fields = append(df.Fields, data.NewField("noise", nil, []float64{sf.Noise})) + + f := data.NewField("period", nil, []float64{sf.Period}) + f.Config = &data.FieldConfig{Unit: "s"} // seconds + df.Fields = append(df.Fields, f) + + return simulationInfo{ + Type: "sine", + Name: "Sine", + Description: "Sinewave generator", + SetupFields: df, + OnlyForward: false, + create: func(cfg simulationState) (Simulation, error) { + s := &waveformSim{ + key: cfg.Key, + cfg: sf, // default value + calculator: sinewaveCalculator, + } + err := updateConfigObjectFromJSON(&s.cfg, cfg.Config) // override any fields + return s, err + }, + } +} + +func sinewaveCalculator(x float64, cfg *waveformConfig) float64 { + return (math.Sin(x) * cfg.Amplitude) + cfg.Offset +} diff --git a/pkg/tsdb/testdatasource/stream_handler.go b/pkg/tsdb/testdatasource/stream_handler.go index c71b9e3b03c..1b7c7a1c40e 100644 --- a/pkg/tsdb/testdatasource/stream_handler.go +++ b/pkg/tsdb/testdatasource/stream_handler.go @@ -15,22 +15,18 @@ import ( var random20HzStreamRegex = regexp.MustCompile(`random-20Hz-stream(-\d+)?`) -func (s *Service) SubscribeStream(_ context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { +func (s *Service) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { s.logger.Debug("Allowing access to stream", "path", req.Path, "user", req.PluginContext.User) + + if strings.HasPrefix(req.Path, "sim/") { + return s.sims.SubscribeStream(ctx, req) + } + initialData, err := backend.NewInitialFrame(s.frame, data.IncludeSchemaOnly) if err != nil { return nil, err } - // For flight simulations, send the more complex schema - if strings.HasPrefix(req.Path, "flight") { - ff := newFlightConfig().initFields() - initialData, err = backend.NewInitialFrame(ff.frame, data.IncludeSchemaOnly) - if err != nil { - return nil, err - } - } - if strings.Contains(req.Path, "-labeled") { initialData, err = backend.NewInitialFrame(s.labelFrame, data.IncludeSchemaOnly) if err != nil { @@ -49,8 +45,13 @@ func (s *Service) SubscribeStream(_ context.Context, req *backend.SubscribeStrea }, nil } -func (s *Service) PublishStream(_ context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) { +func (s *Service) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) { s.logger.Debug("Attempt to publish into stream", "path", req.Path, "user", req.PluginContext.User) + + if strings.HasPrefix(req.Path, "sim/") { + return s.sims.PublishStream(ctx, req) + } + return &backend.PublishStreamResponse{ Status: backend.PublishStreamStatusPermissionDenied, }, nil @@ -58,6 +59,11 @@ func (s *Service) PublishStream(_ context.Context, req *backend.PublishStreamReq func (s *Service) RunStream(ctx context.Context, request *backend.RunStreamRequest, sender *backend.StreamSender) error { s.logger.Debug("New stream call", "path", request.Path) + + if strings.HasPrefix(request.Path, "sim/") { + return s.sims.RunStream(ctx, request, sender) + } + var conf testStreamConfig switch { case request.Path == "random-2s-stream": @@ -75,11 +81,6 @@ func (s *Service) RunStream(ctx context.Context, request *backend.RunStreamReque Drop: 0.2, // keep 80% Labeled: true, } - case request.Path == "flight-5hz-stream": - conf = testStreamConfig{ - Interval: 200 * time.Millisecond, - Flight: newFlightConfig(), - } case random20HzStreamRegex.MatchString(request.Path): conf = testStreamConfig{ Interval: 50 * time.Millisecond, @@ -93,7 +94,6 @@ func (s *Service) RunStream(ctx context.Context, request *backend.RunStreamReque type testStreamConfig struct { Interval time.Duration Drop float64 - Flight *flightConfig Labeled bool } @@ -104,12 +104,6 @@ func (s *Service) runTestStream(ctx context.Context, path string, conf testStrea ticker := time.NewTicker(conf.Interval) defer ticker.Stop() - var flight *flightFields - if conf.Flight != nil { - flight = conf.Flight.initFields() - flight.append(conf.Flight.getNextPoint(time.Now())) - } - labelFrame := data.NewFrame("labeled", data.NewField("labels", nil, make([]string, 2)), data.NewField("Time", nil, make([]time.Time, 2)), @@ -131,37 +125,30 @@ func (s *Service) runTestStream(ctx context.Context, path string, conf testStrea mode = data.IncludeAll } - if flight != nil { - flight.set(0, conf.Flight.getNextPoint(t)) - if err := sender.SendFrame(flight.frame, mode); err != nil { + delta := rand.Float64() - 0.5 + walker += delta + + if conf.Labeled { + secA := t.Second() / 3 + secB := t.Second() / 7 + + labelFrame.Fields[0].Set(0, fmt.Sprintf("s=A,s=p%d,x=X", secA)) + labelFrame.Fields[1].Set(0, t) + labelFrame.Fields[2].Set(0, walker) + + labelFrame.Fields[0].Set(1, fmt.Sprintf("s=B,s=p%d,x=X", secB)) + labelFrame.Fields[1].Set(1, t) + labelFrame.Fields[2].Set(1, walker+10) + if err := sender.SendFrame(labelFrame, mode); err != nil { return err } } else { - delta := rand.Float64() - 0.5 - walker += delta - - if conf.Labeled { - secA := t.Second() / 3 - secB := t.Second() / 7 - - labelFrame.Fields[0].Set(0, fmt.Sprintf("s=A,s=p%d,x=X", secA)) - labelFrame.Fields[1].Set(0, t) - labelFrame.Fields[2].Set(0, walker) - - labelFrame.Fields[0].Set(1, fmt.Sprintf("s=B,s=p%d,x=X", secB)) - labelFrame.Fields[1].Set(1, t) - labelFrame.Fields[2].Set(1, walker+10) - if err := sender.SendFrame(labelFrame, mode); err != nil { - return err - } - } else { - s.frame.Fields[0].Set(0, t) - s.frame.Fields[1].Set(0, walker) // Value - s.frame.Fields[2].Set(0, walker-((rand.Float64()*spread)+0.01)) // Min - s.frame.Fields[3].Set(0, walker+((rand.Float64()*spread)+0.01)) // Max - if err := sender.SendFrame(s.frame, mode); err != nil { - return err - } + s.frame.Fields[0].Set(0, t) + s.frame.Fields[1].Set(0, walker) // Value + s.frame.Fields[2].Set(0, walker-((rand.Float64()*spread)+0.01)) // Min + s.frame.Fields[3].Set(0, walker+((rand.Float64()*spread)+0.01)) // Max + if err := sender.SendFrame(s.frame, mode); err != nil { + return err } } } diff --git a/pkg/tsdb/testdatasource/testdata.go b/pkg/tsdb/testdatasource/testdata.go index b99b735d046..3cedd2f91ef 100644 --- a/pkg/tsdb/testdatasource/testdata.go +++ b/pkg/tsdb/testdatasource/testdata.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tsdb/testdatasource/sims" ) func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles) *Service { @@ -34,6 +35,12 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles) *Serv cfg: cfg, } + var err error + s.sims, err = sims.NewSimulationEngine() + if err != nil { + s.logger.Error("unable to initialize SimulationEngine", "err", err) + } + s.registerScenarios() s.resourceHandler = httpadapter.New(s.registerRoutes()) @@ -49,6 +56,7 @@ type Service struct { queryMux *datasource.QueryTypeMux resourceHandler backend.CallResourceHandler features featuremgmt.FeatureToggles + sims *sims.SimulationEngine } func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { diff --git a/pkg/util/proxyutil/reverse_proxy_test.go b/pkg/util/proxyutil/reverse_proxy_test.go index a886b928d8e..19c7db9144d 100644 --- a/pkg/util/proxyutil/reverse_proxy_test.go +++ b/pkg/util/proxyutil/reverse_proxy_test.go @@ -86,11 +86,12 @@ func TestReverseProxy(t *testing.T) { t.Run("Error handling should convert status codes depending on what kind of error it is", func(t *testing.T) { timedOutTransport := http.DefaultTransport.(*http.Transport) - timedOutTransport.ResponseHeaderTimeout = time.Nanosecond + timedOutTransport.ResponseHeaderTimeout = time.Millisecond testCases := []struct { desc string transport http.RoundTripper + responseWaitTime time.Duration expectedStatusCode int }{ { @@ -101,6 +102,7 @@ func TestReverseProxy(t *testing.T) { { desc: "Timed out request should return 504 Gateway timeout", transport: timedOutTransport, + responseWaitTime: 100 * time.Millisecond, expectedStatusCode: http.StatusGatewayTimeout, }, { @@ -113,6 +115,10 @@ func TestReverseProxy(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { upstream := newUpstreamServer(t, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if tc.responseWaitTime > 0 { + time.Sleep(tc.responseWaitTime) + } + w.WriteHeader(http.StatusOK) })) t.Cleanup(upstream.Close) diff --git a/plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx b/plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx index 975bea26e1f..e79558da14c 100644 --- a/plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx +++ b/plugins-bundled/internal/input-datasource/src/InputConfigEditor.tsx @@ -2,10 +2,10 @@ import React, { PureComponent } from 'react'; // Types -import { InputOptions } from './types'; - -import { TableInputCSV } from '@grafana/ui'; import { DataSourcePluginOptionsEditorProps, DataFrame, MutableDataFrame } from '@grafana/data'; +import { TableInputCSV } from '@grafana/ui'; + +import { InputOptions } from './types'; import { dataFrameToCSV } from './utils'; interface Props extends DataSourcePluginOptionsEditorProps {} diff --git a/plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts b/plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts index 1f20fede1c1..bc8f1f43f56 100644 --- a/plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts +++ b/plugins-bundled/internal/input-datasource/src/InputDatasource.test.ts @@ -1,5 +1,3 @@ -import InputDatasource, { describeDataFrame } from './InputDatasource'; -import { InputOptions, InputQuery } from './types'; import { DataFrame, DataFrameDTO, @@ -9,7 +7,9 @@ import { readCSV, } from '@grafana/data'; +import InputDatasource, { describeDataFrame } from './InputDatasource'; import { getQueryOptions } from './testHelpers'; +import { InputOptions, InputQuery } from './types'; describe('InputDatasource', () => { const data = readCSV('a,b,c\n1,2,3\n4,5,6'); diff --git a/plugins-bundled/internal/input-datasource/src/InputQueryEditor.tsx b/plugins-bundled/internal/input-datasource/src/InputQueryEditor.tsx index e48b89ac31e..a8e3fcf9442 100644 --- a/plugins-bundled/internal/input-datasource/src/InputQueryEditor.tsx +++ b/plugins-bundled/internal/input-datasource/src/InputQueryEditor.tsx @@ -2,12 +2,11 @@ import React, { PureComponent } from 'react'; // Types +import { DataFrame, toCSV, SelectableValue, MutableDataFrame, QueryEditorProps } from '@grafana/data'; +import { Select, TableInputCSV, LinkButton, Icon, InlineField } from '@grafana/ui'; + import { InputDatasource, describeDataFrame } from './InputDatasource'; import { InputQuery, InputOptions } from './types'; - -import { Select, TableInputCSV, LinkButton, Icon, InlineField } from '@grafana/ui'; -import { DataFrame, toCSV, SelectableValue, MutableDataFrame, QueryEditorProps } from '@grafana/data'; - import { dataFrameToCSV } from './utils'; type Props = QueryEditorProps; diff --git a/plugins-bundled/internal/input-datasource/src/module.ts b/plugins-bundled/internal/input-datasource/src/module.ts index 708c95a841e..66765d458c5 100644 --- a/plugins-bundled/internal/input-datasource/src/module.ts +++ b/plugins-bundled/internal/input-datasource/src/module.ts @@ -1,9 +1,8 @@ import { DataSourcePlugin } from '@grafana/data'; -import { InputDatasource } from './InputDatasource'; - -import { InputQueryEditor } from './InputQueryEditor'; import { InputConfigEditor } from './InputConfigEditor'; +import { InputDatasource } from './InputDatasource'; +import { InputQueryEditor } from './InputQueryEditor'; import { InputOptions, InputQuery } from './types'; export const plugin = new DataSourcePlugin(InputDatasource) diff --git a/public/api-merged.json b/public/api-merged.json index 2a329b78226..51601635d42 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -506,14 +506,6 @@ "summary": "Add a user role assignment.", "operationId": "addUserRole", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true - }, { "x-go-name": "Body", "name": "body", @@ -522,6 +514,14 @@ "schema": { "$ref": "#/definitions/AddUserRoleCommand" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true } ], "responses": { @@ -547,14 +547,6 @@ "summary": "Remove a user role assignment.", "operationId": "removeUserRole", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true - }, { "type": "string", "x-go-name": "RoleUID", @@ -568,6 +560,14 @@ "description": "A flag indicating if the assignment is global or not. If set to false, the default org ID of the authenticated user will be used from the request to remove assignment.", "name": "global", "in": "query" + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true } ], "responses": { @@ -2217,7 +2217,7 @@ } } }, - "/alertmanager/{Recipient}/api/v2/alerts": { + "/alertmanager/{DatasourceID}/api/v2/alerts": { "get": { "description": "get alertmanager alerts", "tags": ["alertmanager"], @@ -2267,8 +2267,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2306,8 +2306,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2328,7 +2328,7 @@ } } }, - "/alertmanager/{Recipient}/api/v2/alerts/groups": { + "/alertmanager/{DatasourceID}/api/v2/alerts/groups": { "get": { "description": "get alertmanager alerts", "tags": ["alertmanager"], @@ -2378,8 +2378,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2400,7 +2400,7 @@ } } }, - "/alertmanager/{Recipient}/api/v2/silence/{SilenceId}": { + "/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}": { "get": { "description": "get silence", "tags": ["alertmanager"], @@ -2415,8 +2415,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2450,8 +2450,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2472,7 +2472,7 @@ } } }, - "/alertmanager/{Recipient}/api/v2/silences": { + "/alertmanager/{DatasourceID}/api/v2/silences": { "get": { "description": "get silences", "tags": ["alertmanager"], @@ -2490,8 +2490,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2526,8 +2526,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2548,7 +2548,7 @@ } } }, - "/alertmanager/{Recipient}/api/v2/status": { + "/alertmanager/{DatasourceID}/api/v2/status": { "get": { "description": "get alertmanager status and configuration", "tags": ["alertmanager"], @@ -2557,8 +2557,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2579,7 +2579,7 @@ } } }, - "/alertmanager/{Recipient}/config/api/v1/alerts": { + "/alertmanager/{DatasourceID}/config/api/v1/alerts": { "get": { "description": "gets an Alerting config", "tags": ["alertmanager"], @@ -2588,8 +2588,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2624,8 +2624,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2653,8 +2653,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -2675,7 +2675,7 @@ } } }, - "/alertmanager/{Recipient}/config/api/v1/receivers/test": { + "/alertmanager/{DatasourceID}/config/api/v1/receivers/test": { "post": { "tags": ["alertmanager"], "summary": "Test Grafana managed receivers without saving them.", @@ -2691,8 +2691,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -3564,9 +3564,11 @@ }, "/dashboards/id/{DashboardID}/permissions": { "get": { + "description": "Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsWithUid) instead", "tags": ["dashboard_permissions"], "summary": "Gets all existing permissions for the given dashboard.", "operationId": "getDashboardPermissions", + "deprecated": true, "parameters": [ { "type": "integer", @@ -3595,10 +3597,11 @@ } }, "post": { - "description": "This operation will remove existing permissions if they’re not included in the request.", + "description": "Please refer to [updated API](#/dashboard_permissions/postDashboardPermissionsWithUid) instead\n\nThis operation will remove existing permissions if they’re not included in the request.", "tags": ["dashboard_permissions"], "summary": "Updates permissions for a dashboard.", "operationId": "postDashboardPermissions", + "deprecated": true, "parameters": [ { "name": "Body", @@ -3911,6 +3914,82 @@ } } }, + "/dashboards/uid/{uid}/permissions": { + "get": { + "tags": ["dashboard_permissions"], + "summary": "Gets all existing permissions for the given dashboard.", + "operationId": "getDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/getDashboardPermissionsResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "This operation will remove existing permissions if they’re not included in the request.", + "tags": ["dashboard_permissions"], + "summary": "Updates permissions for a dashboard.", + "operationId": "postDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpdateDashboardAclCommand" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources": { "get": { "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scope: `datasources:*`.", @@ -4625,15 +4704,15 @@ "parameters": [ { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true }, { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true } @@ -6550,7 +6629,7 @@ } } }, - "/prometheus/{Recipient}/api/v1/alerts": { + "/prometheus/{DatasourceID}/api/v1/alerts": { "get": { "description": "gets the current alerts", "tags": ["prometheus"], @@ -6559,8 +6638,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -6575,7 +6654,7 @@ } } }, - "/prometheus/{Recipient}/api/v1/rules": { + "/prometheus/{DatasourceID}/api/v1/rules": { "get": { "description": "gets the evaluation statuses of all rules", "tags": ["prometheus"], @@ -6584,8 +6663,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true } @@ -7487,7 +7566,7 @@ } } }, - "/ruler/{Recipient}/api/v1/rules": { + "/ruler/{DatasourceID}/api/v1/rules": { "get": { "description": "List rule groups", "produces": ["application/json"], @@ -7497,8 +7576,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -7524,7 +7603,7 @@ } } }, - "/ruler/{Recipient}/api/v1/rules/{Namespace}": { + "/ruler/{DatasourceID}/api/v1/rules/{Namespace}": { "get": { "description": "Get rule groups by namespace", "produces": ["application/json"], @@ -7534,8 +7613,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -7564,8 +7643,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -7600,8 +7679,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -7622,7 +7701,7 @@ } } }, - "/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}": { + "/ruler/{DatasourceID}/api/v1/rules/{Namespace}/{Groupname}": { "get": { "description": "Get rule group", "produces": ["application/json"], @@ -7632,8 +7711,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -7667,8 +7746,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -9457,7 +9536,7 @@ } } }, - "/v1/rule/test/{Recipient}": { + "/v1/rule/test/{DatasourceID}": { "post": { "description": "Test a rule against external data source ruler", "consumes": ["application/json"], @@ -9468,8 +9547,8 @@ { "type": "integer", "format": "int64", - "description": "Recipient should be the numeric datasource id", - "name": "Recipient", + "description": "DatasourceID should be the numeric datasource id", + "name": "DatasourceID", "in": "path", "required": true }, @@ -9550,7 +9629,10 @@ "x-go-name": "Name" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "secondsToLive": { "type": "integer", @@ -9660,7 +9742,10 @@ "x-go-name": "Name" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "sendEmail": { "type": "boolean", @@ -9677,7 +9762,10 @@ "x-go-name": "LoginOrEmail" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -10440,7 +10528,10 @@ "x-go-name": "Name" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -11039,7 +11130,10 @@ "x-go-name": "PermissionName" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "slug": { "type": "string", @@ -11106,7 +11200,10 @@ "$ref": "#/definitions/PermissionType" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "teamId": { "type": "integer", @@ -11917,6 +12014,7 @@ "x-go-name": "DisableResolveMessage" }, "name": { + "description": "Name is used as grouping key in the UI. Contact points with the\nsame name will be grouped in the UI.", "type": "string", "x-go-name": "Name" }, @@ -11932,6 +12030,7 @@ "x-go-name": "Type" }, "uid": { + "description": "UID is the unique identifier of the contact point. This will be\nautomatically set be the Grafana.", "type": "string", "x-go-name": "UID" } @@ -13501,7 +13600,7 @@ "properties": { "id": { "type": "string", - "x-go-name": "Id" + "x-go-name": "ID" }, "target": { "type": "string", @@ -13516,7 +13615,7 @@ "x-go-name": "Url" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "NavbarPreference": { "type": "object", @@ -14520,6 +14619,10 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/recordedqueries/api" }, + "Provenance": { + "type": "string", + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models" + }, "PushoverConfig": { "type": "object", "properties": { @@ -14592,7 +14695,7 @@ "x-go-name": "HomeTab" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "Receiver": { "type": "object", @@ -14876,10 +14979,6 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/accesscontrol" }, - "RoleType": { - "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/models" - }, "Route": { "description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.", "type": "object", @@ -14925,6 +15024,9 @@ "object_matchers": { "$ref": "#/definitions/ObjectMatchers" }, + "provenance": { + "$ref": "#/definitions/Provenance" + }, "receiver": { "type": "string", "x-go-name": "Receiver" @@ -15857,7 +15959,10 @@ "x-go-name": "OrgId" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "status": { "$ref": "#/definitions/TempUserStatus" @@ -16538,7 +16643,10 @@ "type": "object", "properties": { "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -16727,7 +16835,10 @@ "x-go-name": "OrgId" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -17126,7 +17237,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { - "description": "AlertGroup alert group", "type": "object", "required": ["alerts", "labels", "receiver"], "properties": { @@ -17144,14 +17254,17 @@ "receiver": { "$ref": "#/definitions/receiver" } - } + }, + "x-go-name": "AlertGroup", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroups": { - "description": "AlertGroups alert groups", "type": "array", "items": { "$ref": "#/definitions/alertGroup" - } + }, + "x-go-name": "AlertGroups", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertStatus": { "description": "AlertStatus alert status", @@ -17254,6 +17367,7 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { + "description": "GettableAlert gettable alert", "type": "object", "required": ["labels", "annotations", "endsAt", "fingerprint", "receivers", "startsAt", "status", "updatedAt"], "properties": { @@ -17303,9 +17417,7 @@ "format": "date-time", "x-go-name": "UpdatedAt" } - }, - "x-go-name": "GettableAlert", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "gettableAlerts": { "description": "GettableAlerts gettable alerts", @@ -17315,7 +17427,6 @@ } }, "gettableSilence": { - "description": "GettableSilence gettable silence", "type": "object", "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt", "id", "status", "updatedAt"], "properties": { @@ -17358,14 +17469,17 @@ "format": "date-time", "x-go-name": "UpdatedAt" } - } + }, + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "$ref": "#/definitions/gettableSilence" - } + }, + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "labelSet": { "description": "LabelSet label set", @@ -17523,6 +17637,7 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { + "description": "Receiver receiver", "type": "object", "required": ["name"], "properties": { @@ -17531,9 +17646,7 @@ "type": "string", "x-go-name": "Name" } - }, - "x-go-name": "Receiver", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "silence": { "description": "Silence silence", diff --git a/public/api-spec.json b/public/api-spec.json index 117b03382c3..bf38c563b31 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -506,14 +506,6 @@ "summary": "Add a user role assignment.", "operationId": "addUserRole", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true - }, { "x-go-name": "Body", "name": "body", @@ -522,6 +514,14 @@ "schema": { "$ref": "#/definitions/AddUserRoleCommand" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true } ], "responses": { @@ -547,14 +547,6 @@ "summary": "Remove a user role assignment.", "operationId": "removeUserRole", "parameters": [ - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true - }, { "type": "string", "x-go-name": "RoleUID", @@ -568,6 +560,14 @@ "description": "A flag indicating if the assignment is global or not. If set to false, the default org ID of the authenticated user will be used from the request to remove assignment.", "name": "global", "in": "query" + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true } ], "responses": { @@ -2614,9 +2614,11 @@ }, "/dashboards/id/{DashboardID}/permissions": { "get": { + "description": "Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsWithUid) instead", "tags": ["dashboard_permissions"], "summary": "Gets all existing permissions for the given dashboard.", "operationId": "getDashboardPermissions", + "deprecated": true, "parameters": [ { "type": "integer", @@ -2645,10 +2647,11 @@ } }, "post": { - "description": "This operation will remove existing permissions if they’re not included in the request.", + "description": "Please refer to [updated API](#/dashboard_permissions/postDashboardPermissionsWithUid) instead\n\nThis operation will remove existing permissions if they’re not included in the request.", "tags": ["dashboard_permissions"], "summary": "Updates permissions for a dashboard.", "operationId": "postDashboardPermissions", + "deprecated": true, "parameters": [ { "name": "Body", @@ -2961,6 +2964,82 @@ } } }, + "/dashboards/uid/{uid}/permissions": { + "get": { + "tags": ["dashboard_permissions"], + "summary": "Gets all existing permissions for the given dashboard.", + "operationId": "getDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/getDashboardPermissionsResponse" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "This operation will remove existing permissions if they’re not included in the request.", + "tags": ["dashboard_permissions"], + "summary": "Updates permissions for a dashboard.", + "operationId": "postDashboardPermissionsWithUid", + "parameters": [ + { + "type": "string", + "x-go-name": "UID", + "name": "uid", + "in": "path", + "required": true + }, + { + "name": "Body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpdateDashboardAclCommand" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/okResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources": { "get": { "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scope: `datasources:*`.", @@ -3675,15 +3754,15 @@ "parameters": [ { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true }, { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true } @@ -7797,7 +7876,10 @@ "x-go-name": "Name" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "secondsToLive": { "type": "integer", @@ -7907,7 +7989,10 @@ "x-go-name": "Name" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "sendEmail": { "type": "boolean", @@ -7924,7 +8009,10 @@ "x-go-name": "LoginOrEmail" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -8553,7 +8641,10 @@ "x-go-name": "Name" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -9047,7 +9138,10 @@ "x-go-name": "PermissionName" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "slug": { "type": "string", @@ -9114,7 +9208,10 @@ "$ref": "#/definitions/PermissionType" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "teamId": { "type": "integer", @@ -10610,7 +10707,7 @@ "properties": { "id": { "type": "string", - "x-go-name": "Id" + "x-go-name": "ID" }, "target": { "type": "string", @@ -10625,7 +10722,7 @@ "x-go-name": "Url" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "NavbarPreference": { "type": "object", @@ -11074,7 +11171,7 @@ "x-go-name": "HomeTab" } }, - "x-go-package": "github.com/grafana/grafana/pkg/models" + "x-go-package": "github.com/grafana/grafana/pkg/services/preference" }, "RecordingRuleJSON": { "description": "RecordingRuleJSON is the external representation of a recording rule", @@ -11254,10 +11351,6 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/accesscontrol" }, - "RoleType": { - "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/models" - }, "SaveDashboardCommand": { "type": "object", "properties": { @@ -11709,7 +11802,10 @@ "x-go-name": "OrgId" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" }, "status": { "$ref": "#/definitions/TempUserStatus" @@ -12181,7 +12277,10 @@ "type": "object", "properties": { "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -12370,7 +12469,10 @@ "x-go-name": "OrgId" }, "role": { - "$ref": "#/definitions/RoleType" + "type": "string", + "enum": ["Viewer", "Editor", "Admin"], + "x-go-enum-desc": "Viewer ROLE_VIEWER\nEditor ROLE_EDITOR\nAdmin ROLE_ADMIN", + "x-go-name": "Role" } }, "x-go-package": "github.com/grafana/grafana/pkg/models" diff --git a/public/app/AppWrapper.tsx b/public/app/AppWrapper.tsx index 95c0477196a..812e57c72e0 100644 --- a/public/app/AppWrapper.tsx +++ b/public/app/AppWrapper.tsx @@ -1,23 +1,27 @@ +import { Action, KBarProvider } from 'kbar'; import React, { ComponentType } from 'react'; -import { Router, Route, Redirect, Switch } from 'react-router-dom'; -import { config, locationService, navigationLogger } from '@grafana/runtime'; import { Provider } from 'react-redux'; -import { store } from 'app/store/store'; +import { Router, Route, Redirect, Switch } from 'react-router-dom'; + +import { config, locationService, navigationLogger, reportInteraction } from '@grafana/runtime'; import { ErrorBoundaryAlert, GlobalStyles, ModalRoot, ModalsProvider, PortalContainer } from '@grafana/ui'; -import { GrafanaApp } from './app'; -import { getAppRoutes } from 'app/routes/routes'; -import { ConfigContext, ThemeProvider } from './core/utils/ConfigProvider'; -import { RouteDescriptor } from './core/navigation/types'; -import { contextSrv } from './core/services/context_srv'; -import { NavBar } from './core/components/NavBar/NavBar'; -import { NavBarNext } from './core/components/NavBar/Next/NavBarNext'; -import { GrafanaRoute } from './core/navigation/GrafanaRoute'; -import { AppNotificationList } from './core/components/AppNotifications/AppNotificationList'; import { SearchWrapper } from 'app/features/search'; -import { LiveConnectionWarning } from './features/live/LiveConnectionWarning'; -import { I18nProvider } from './core/localisation'; +import { getAppRoutes } from 'app/routes/routes'; +import { store } from 'app/store/store'; + import { AngularRoot } from './angular/AngularRoot'; import { loadAndInitAngularIfEnabled } from './angular/loadAndInitAngularIfEnabled'; +import { GrafanaApp } from './app'; +import { AppNotificationList } from './core/components/AppNotifications/AppNotificationList'; +import { NavBar } from './core/components/NavBar/NavBar'; +import { NavBarNext } from './core/components/NavBar/Next/NavBarNext'; +import { I18nProvider } from './core/localisation'; +import { GrafanaRoute } from './core/navigation/GrafanaRoute'; +import { RouteDescriptor } from './core/navigation/types'; +import { contextSrv } from './core/services/context_srv'; +import { ConfigContext, ThemeProvider } from './core/utils/ConfigProvider'; +import { CommandPalette } from './features/commandPalette/CommandPalette'; +import { LiveConnectionWarning } from './features/live/LiveConnectionWarning'; interface AppWrapperProps { app: GrafanaApp; @@ -85,36 +89,48 @@ export class AppWrapper extends React.Component { + reportInteraction('commandPalette_action_selected', { + actionId: action.id, + }); + }; + return ( - - -
- - {newNavigationEnabled ? : } -
- {pageBanners.map((Banner, index) => ( - - ))} + + + + {config.featureToggles.commandPalette && } +
+ + {ready && <>{newNavigationEnabled ? : }} +
+ {pageBanners.map((Banner, index) => ( + + ))} - - - - {ready && this.renderRoutes()} - {bodyRenderHooks.map((Hook, index) => ( - - ))} -
-
-
- - - -
+ + + + {ready && this.renderRoutes()} + {bodyRenderHooks.map((Hook, index) => ( + + ))} +
+
+
+ + + +
+
diff --git a/public/app/angular/AngularApp.ts b/public/app/angular/AngularApp.ts index 29977589ea9..7bbdeba9768 100644 --- a/public/app/angular/AngularApp.ts +++ b/public/app/angular/AngularApp.ts @@ -1,24 +1,28 @@ -import angular from 'angular'; +import 'angular'; import 'angular-route'; import 'angular-sanitize'; import 'angular-bindonce'; import 'vendor/bootstrap/bootstrap'; import 'vendor/angular-other/angular-strap'; -import { config } from 'app/core/config'; + +import angular from 'angular'; // eslint-disable-line no-duplicate-imports +import { extend } from 'lodash'; + +import { getTemplateSrv } from '@grafana/runtime'; import coreModule, { angularModules } from 'app/angular/core_module'; +import appEvents from 'app/core/app_events'; +import { config } from 'app/core/config'; +import { contextSrv } from 'app/core/services/context_srv'; import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { exposeToPlugin } from 'app/features/plugins/plugin_loader'; +import * as sdk from 'app/plugins/sdk'; + import { registerAngularDirectives } from './angular_wrappers'; import { initAngularRoutingBridge } from './bridgeReactAngularRouting'; import { monkeyPatchInjectorWithPreAssignedBindings } from './injectorMonkeyPatch'; -import { extend } from 'lodash'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { getTemplateSrv } from '@grafana/runtime'; -import { registerComponents } from './registerComponents'; -import { exposeToPlugin } from 'app/features/plugins/plugin_loader'; -import appEvents from 'app/core/app_events'; -import { contextSrv } from 'app/core/services/context_srv'; -import * as sdk from 'app/plugins/sdk'; import { promiseToDigest } from './promiseToDigest'; +import { registerComponents } from './registerComponents'; export class AngularApp { ngModuleDependencies: any[]; diff --git a/public/app/angular/AngularLocationWrapper.test.ts b/public/app/angular/AngularLocationWrapper.test.ts index fb5358d57fc..613e570af37 100644 --- a/public/app/angular/AngularLocationWrapper.test.ts +++ b/public/app/angular/AngularLocationWrapper.test.ts @@ -1,6 +1,7 @@ -import { AngularLocationWrapper } from './AngularLocationWrapper'; import { HistoryWrapper, locationService, setLocationService } from '@grafana/runtime'; +import { AngularLocationWrapper } from './AngularLocationWrapper'; + // The methods in this file are deprecated // Stub the deprecation warning here to prevent polluting the test output jest.mock('@grafana/data', () => ({ diff --git a/public/app/angular/AngularLocationWrapper.ts b/public/app/angular/AngularLocationWrapper.ts index 4c6f5c133c2..3da42635bd2 100644 --- a/public/app/angular/AngularLocationWrapper.ts +++ b/public/app/angular/AngularLocationWrapper.ts @@ -1,5 +1,5 @@ -import { locationSearchToObject, locationService, navigationLogger } from '@grafana/runtime'; import { deprecationWarning, urlUtil } from '@grafana/data'; +import { locationSearchToObject, locationService, navigationLogger } from '@grafana/runtime'; // Ref: https://github.com/angular/angular.js/blob/ae8e903edf88a83fedd116ae02c0628bf72b150c/src/ng/location.js#L5 const DEFAULT_PORTS: Record = { http: 80, https: 443, ftp: 21 }; diff --git a/public/app/angular/GrafanaCtrl.ts b/public/app/angular/GrafanaCtrl.ts index 0b2b9b88d5c..405c6729613 100644 --- a/public/app/angular/GrafanaCtrl.ts +++ b/public/app/angular/GrafanaCtrl.ts @@ -1,23 +1,19 @@ -// Libraries -// eslint-disable-next-line lodash/import-scope -import _ from 'lodash'; -import $ from 'jquery'; - -// Utils and servies -import { colors } from '@grafana/ui'; -import { setLegacyAngularInjector, setAppEvents, setAngularLoader } from '@grafana/runtime'; -import config from 'app/core/config'; -import coreModule from 'app/angular/core_module'; -import appEvents from 'app/core/app_events'; -import { AngularLoader } from 'app/angular/services/AngularLoader'; - -// Types -import { CoreEvents, AppEventEmitter, AppEventConsumer } from 'app/types'; -import { UtilSrv } from './services/UtilSrv'; -import { ContextSrv } from 'app/core/services/context_srv'; import { IRootScopeService, IAngularEvent, auto } from 'angular'; +import $ from 'jquery'; +import _ from 'lodash'; // eslint-disable-line lodash/import-scope + import { AppEvent } from '@grafana/data'; +import { setLegacyAngularInjector, setAppEvents, setAngularLoader } from '@grafana/runtime'; +import { colors } from '@grafana/ui'; +import coreModule from 'app/angular/core_module'; +import { AngularLoader } from 'app/angular/services/AngularLoader'; +import appEvents from 'app/core/app_events'; +import config from 'app/core/config'; +import { ContextSrv } from 'app/core/services/context_srv'; import { initGrafanaLive } from 'app/features/live'; +import { CoreEvents, AppEventEmitter, AppEventConsumer } from 'app/types'; + +import { UtilSrv } from './services/UtilSrv'; export type GrafanaRootScope = IRootScopeService & AppEventEmitter & AppEventConsumer & { colors: string[] }; diff --git a/public/app/angular/angular_wrappers.ts b/public/app/angular/angular_wrappers.ts index e70b00537cc..11d77c90a1d 100644 --- a/public/app/angular/angular_wrappers.ts +++ b/public/app/angular/angular_wrappers.ts @@ -1,11 +1,3 @@ -import { react2AngularDirective } from 'app/angular/react2angular'; -import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/QueryEditor'; -import { AnnotationQueryEditor as CloudMonitoringAnnotationQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor'; -import { AnnotationQueryEditor as CloudWatchAnnotationQueryEditor } from 'app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor'; -import PageHeader from '../core/components/PageHeader/PageHeader'; -import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA'; -import { TagFilter } from '../core/components/TagFilter/TagFilter'; -import { MetricSelect } from '../core/components/Select/MetricSelect'; import { ClipboardButton, ColorPicker, @@ -18,13 +10,20 @@ import { Spinner, UnitPicker, } from '@grafana/ui'; -import { LokiAnnotationsQueryEditor } from '../plugins/datasource/loki/components/AnnotationsQueryEditor'; -import { HelpModal } from '../core/components/help/HelpModal'; -import { Footer } from '../core/components/Footer/Footer'; +import { react2AngularDirective } from 'app/angular/react2angular'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; -import { SearchField, SearchResults, SearchResultsFilter } from '../features/search'; import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings'; -import QueryEditor from 'app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor'; +import { AnnotationQueryEditor as CloudMonitoringAnnotationQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor'; +import { QueryEditor as CloudMonitoringQueryEditor } from 'app/plugins/datasource/cloud-monitoring/components/QueryEditor'; + +import EmptyListCTA from '../core/components/EmptyListCTA/EmptyListCTA'; +import { Footer } from '../core/components/Footer/Footer'; +import PageHeader from '../core/components/PageHeader/PageHeader'; +import { MetricSelect } from '../core/components/Select/MetricSelect'; +import { TagFilter } from '../core/components/TagFilter/TagFilter'; +import { HelpModal } from '../core/components/help/HelpModal'; +import { SearchField, SearchResults, SearchResultsFilter } from '../features/search'; +import { LokiAnnotationsQueryEditor } from '../plugins/datasource/loki/components/AnnotationsQueryEditor'; const { SecretFormField } = LegacyForms; @@ -124,11 +123,7 @@ export function registerAngularDirectives() { ['datasource', { watchDepth: 'reference' }], ['templateSrv', { watchDepth: 'reference' }], ]); - react2AngularDirective('cloudwatchAnnotationQueryEditor', CloudWatchAnnotationQueryEditor, [ - 'query', - 'onChange', - ['datasource', { watchDepth: 'reference' }], - ]); + react2AngularDirective('secretFormField', SecretFormField, [ 'value', 'isConfigured', @@ -197,12 +192,6 @@ export function registerAngularDirectives() { ['onHideTimePickerChange', { watchDepth: 'reference', wrapApply: true }], ]); - react2AngularDirective('azureMonitorQueryEditor', QueryEditor, [ - 'query', - ['datasource', { watchDepth: 'reference' }], - 'onChange', - ]); - react2AngularDirective('clipboardButton', ClipboardButton, [ ['getText', { watchDepth: 'reference', wrapApply: true }], ]); diff --git a/public/app/angular/array_join.ts b/public/app/angular/array_join.ts index b81d3596760..b57e1157b5f 100644 --- a/public/app/angular/array_join.ts +++ b/public/app/angular/array_join.ts @@ -1,4 +1,5 @@ import { isArray } from 'lodash'; + import coreModule from './core_module'; export function arrayJoin() { diff --git a/public/app/angular/bridgeReactAngularRouting.ts b/public/app/angular/bridgeReactAngularRouting.ts index bdfd5c09e28..1b10ce4da95 100644 --- a/public/app/angular/bridgeReactAngularRouting.ts +++ b/public/app/angular/bridgeReactAngularRouting.ts @@ -1,8 +1,10 @@ -import { coreModule } from './core_module'; -import { RouteProvider } from '../core/navigation/patch/RouteProvider'; -import { RouteParamsProvider } from '../core/navigation/patch/RouteParamsProvider'; import { ILocationService } from 'angular'; + +import { RouteParamsProvider } from '../core/navigation/patch/RouteParamsProvider'; +import { RouteProvider } from '../core/navigation/patch/RouteProvider'; + import { AngularLocationWrapper } from './AngularLocationWrapper'; +import { coreModule } from './core_module'; // Neutralizing Angular’s location tampering // https://stackoverflow.com/a/19825756 diff --git a/public/app/angular/components/form_dropdown/form_dropdown.ts b/public/app/angular/components/form_dropdown/form_dropdown.ts index 3fbec41def6..3f22ac329f7 100644 --- a/public/app/angular/components/form_dropdown/form_dropdown.ts +++ b/public/app/angular/components/form_dropdown/form_dropdown.ts @@ -1,6 +1,7 @@ -import { debounce, find, indexOf, map, isObject, escape, unescape } from 'lodash'; -import coreModule from '../../core_module'; import { ISCEService } from 'angular'; +import { debounce, find, indexOf, map, isObject, escape, unescape } from 'lodash'; + +import coreModule from '../../core_module'; import { promiseToDigest } from '../../promiseToDigest'; function typeaheadMatcher(this: any, item: string) { diff --git a/public/app/angular/components/info_popover.ts b/public/app/angular/components/info_popover.ts index 37fe61de5ba..41417f5a680 100644 --- a/public/app/angular/components/info_popover.ts +++ b/public/app/angular/components/info_popover.ts @@ -1,8 +1,9 @@ import { each } from 'lodash'; -import coreModule from 'app/angular/core_module'; // @ts-ignore import Drop from 'tether-drop'; +import coreModule from 'app/angular/core_module'; + export function infoPopover() { return { restrict: 'E', diff --git a/public/app/angular/components/jsontree.ts b/public/app/angular/components/jsontree.ts index 1628de803f0..4721ffcccaa 100644 --- a/public/app/angular/components/jsontree.ts +++ b/public/app/angular/components/jsontree.ts @@ -1,5 +1,5 @@ -import coreModule from 'app/angular/core_module'; import { JsonExplorer } from '@grafana/ui'; +import coreModule from 'app/angular/core_module'; coreModule.directive('jsonTree', [ function jsonTreeDirective() { diff --git a/public/app/angular/components/plugin_component.ts b/public/app/angular/components/plugin_component.ts index be9b7a8e450..0e6bd400d4f 100644 --- a/public/app/angular/components/plugin_component.ts +++ b/public/app/angular/components/plugin_component.ts @@ -1,12 +1,12 @@ import angular, { ILocationService } from 'angular'; import { each } from 'lodash'; -import config from 'app/core/config'; -import coreModule from 'app/angular/core_module'; - import { DataSourceApi, PanelEvents } from '@grafana/data'; -import { importDataSourcePlugin, importAppPlugin } from '../../features/plugins/plugin_loader'; +import coreModule from 'app/angular/core_module'; +import config from 'app/core/config'; + import { importPanelPlugin } from '../../features/plugins/importPanelPlugin'; +import { importDataSourcePlugin, importAppPlugin } from '../../features/plugins/plugin_loader'; /** @ngInject */ function pluginDirectiveLoader($compile: any, $http: any, $templateCache: any, $location: ILocationService) { diff --git a/public/app/angular/components/query_part_editor.ts b/public/app/angular/components/query_part_editor.ts index 3f85e55498b..e6235609de7 100644 --- a/public/app/angular/components/query_part_editor.ts +++ b/public/app/angular/components/query_part_editor.ts @@ -1,6 +1,8 @@ -import { debounce, each, map, partial, escape, unescape } from 'lodash'; import $ from 'jquery'; +import { debounce, each, map, partial, escape, unescape } from 'lodash'; + import coreModule from 'app/angular/core_module'; + import { promiseToDigest } from '../promiseToDigest'; const template = ` diff --git a/public/app/angular/components/scroll.ts b/public/app/angular/components/scroll.ts index 961241a1068..c9fbe69a515 100644 --- a/public/app/angular/components/scroll.ts +++ b/public/app/angular/components/scroll.ts @@ -1,6 +1,7 @@ -import $ from 'jquery'; // @ts-ignore import baron from 'baron'; +import $ from 'jquery'; + import coreModule from 'app/angular/core_module'; const scrollBarHTML = ` diff --git a/public/app/angular/components/sql_part/sql_part_editor.ts b/public/app/angular/components/sql_part/sql_part_editor.ts index 29bf91e9ca5..4f5c38c404b 100644 --- a/public/app/angular/components/sql_part/sql_part_editor.ts +++ b/public/app/angular/components/sql_part/sql_part_editor.ts @@ -1,5 +1,6 @@ -import { debounce, each, indexOf, map, partial, escape, unescape } from 'lodash'; import $ from 'jquery'; +import { debounce, each, indexOf, map, partial, escape, unescape } from 'lodash'; + import coreModule from 'app/angular/core_module'; const template = ` diff --git a/public/app/angular/diff-view.ts b/public/app/angular/diff-view.ts index 664edd0f381..d9b8c000d5b 100644 --- a/public/app/angular/diff-view.ts +++ b/public/app/angular/diff-view.ts @@ -1,7 +1,9 @@ import angular from 'angular'; -import coreModule from './core_module'; + import { GrafanaRootScope } from 'app/angular/GrafanaCtrl'; +import coreModule from './core_module'; + export class DeltaCtrl { observer: any; diff --git a/public/app/angular/dropdown_typeahead.ts b/public/app/angular/dropdown_typeahead.ts index c824c1a4bf8..8c803473029 100644 --- a/public/app/angular/dropdown_typeahead.ts +++ b/public/app/angular/dropdown_typeahead.ts @@ -1,5 +1,6 @@ -import { each, reduce } from 'lodash'; import $ from 'jquery'; +import { each, reduce } from 'lodash'; + import coreModule from './core_module'; /** @ngInject */ diff --git a/public/app/angular/filters/filters.ts b/public/app/angular/filters/filters.ts index c06d15547f2..3f8c7521ac4 100644 --- a/public/app/angular/filters/filters.ts +++ b/public/app/angular/filters/filters.ts @@ -1,8 +1,10 @@ -import { isArray, isNull, isObject, isUndefined } from 'lodash'; import angular from 'angular'; -import coreModule from '../core_module'; -import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; +import { isArray, isNull, isObject, isUndefined } from 'lodash'; + import { dateTime } from '@grafana/data'; +import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; + +import coreModule from '../core_module'; coreModule.filter('stringSort', () => { return (input: any) => { diff --git a/public/app/angular/jquery_extended.ts b/public/app/angular/jquery_extended.ts index e0fb3b9da5a..d878c87d188 100644 --- a/public/app/angular/jquery_extended.ts +++ b/public/app/angular/jquery_extended.ts @@ -1,5 +1,5 @@ -import $ from 'jquery'; import angular from 'angular'; +import $ from 'jquery'; import { extend } from 'lodash'; const $win = $(window); diff --git a/public/app/angular/metric_segment.ts b/public/app/angular/metric_segment.ts index d9a31182e55..cb171ac144e 100644 --- a/public/app/angular/metric_segment.ts +++ b/public/app/angular/metric_segment.ts @@ -1,8 +1,10 @@ -import { debounce, find, indexOf, map, escape, unescape } from 'lodash'; import $ from 'jquery'; -import coreModule from './core_module'; +import { debounce, find, indexOf, map, escape, unescape } from 'lodash'; + import { TemplateSrv } from 'app/features/templating/template_srv'; +import coreModule from './core_module'; + /** @ngInject */ export function metricSegment($compile: any, $sce: any, templateSrv: TemplateSrv) { const inputTemplate = diff --git a/public/app/angular/misc.ts b/public/app/angular/misc.ts index bd31ad80fdf..20899014740 100644 --- a/public/app/angular/misc.ts +++ b/public/app/angular/misc.ts @@ -1,4 +1,5 @@ import angular from 'angular'; + import coreModule from './core_module'; /** @ngInject */ diff --git a/public/app/angular/ng_model_on_blur.ts b/public/app/angular/ng_model_on_blur.ts index d79c60a9f3a..bd730cfc48b 100644 --- a/public/app/angular/ng_model_on_blur.ts +++ b/public/app/angular/ng_model_on_blur.ts @@ -1,6 +1,7 @@ -import coreModule from './core_module'; import { rangeUtil } from '@grafana/data'; +import coreModule from './core_module'; + function ngModelOnBlur() { return { restrict: 'A', diff --git a/public/app/angular/panel/metrics_panel_ctrl.ts b/public/app/angular/panel/metrics_panel_ctrl.ts index 530e945c8fb..a8f3b444181 100644 --- a/public/app/angular/panel/metrics_panel_ctrl.ts +++ b/public/app/angular/panel/metrics_panel_ctrl.ts @@ -1,7 +1,6 @@ import { isArray } from 'lodash'; -import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; -import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; -import { ContextSrv } from 'app/core/services/context_srv'; +import { Unsubscribable } from 'rxjs'; + import { DataFrame, DataQueryResponse, @@ -14,8 +13,11 @@ import { toDataFrameDTO, toLegacyResponseData, } from '@grafana/data'; -import { Unsubscribable } from 'rxjs'; +import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; +import { ContextSrv } from 'app/core/services/context_srv'; import { PanelModel } from 'app/features/dashboard/state'; +import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; + import { PanelQueryRunner } from '../../features/query/state/PanelQueryRunner'; class MetricsPanelCtrl extends PanelCtrl { diff --git a/public/app/angular/panel/panel_ctrl.ts b/public/app/angular/panel/panel_ctrl.ts index 2abb1c49531..249bb84c41b 100644 --- a/public/app/angular/panel/panel_ctrl.ts +++ b/public/app/angular/panel/panel_ctrl.ts @@ -1,7 +1,6 @@ -import { isString } from 'lodash'; -import config from 'app/core/config'; -import { profiler } from 'app/core/core'; import { auto } from 'angular'; +import { isString } from 'lodash'; + import { AppEvent, PanelEvents, @@ -10,8 +9,11 @@ import { EventBusExtended, EventBusSrv, } from '@grafana/data'; -import { DashboardModel } from '../../features/dashboard/state'; import { AngularLocationWrapper } from 'app/angular/AngularLocationWrapper'; +import config from 'app/core/config'; +import { profiler } from 'app/core/core'; + +import { DashboardModel } from '../../features/dashboard/state'; export class PanelCtrl { panel: any; diff --git a/public/app/angular/panel/panel_directive.ts b/public/app/angular/panel/panel_directive.ts index 81e18557961..44cc10856db 100644 --- a/public/app/angular/panel/panel_directive.ts +++ b/public/app/angular/panel/panel_directive.ts @@ -1,12 +1,15 @@ // @ts-ignore import baron from 'baron'; -import { PanelEvents } from '@grafana/data'; -import { PanelModel } from '../../features/dashboard/state'; -import { PanelCtrl } from './panel_ctrl'; import { Subscription } from 'rxjs'; -import { PanelDirectiveReadyEvent, RenderEvent } from 'app/types/events'; -import { coreModule } from 'app/angular/core_module'; + +import { PanelEvents } from '@grafana/data'; import { RefreshEvent } from '@grafana/runtime'; +import { coreModule } from 'app/angular/core_module'; +import { PanelDirectiveReadyEvent, RenderEvent } from 'app/types/events'; + +import { PanelModel } from '../../features/dashboard/state'; + +import { PanelCtrl } from './panel_ctrl'; const panelTemplate = ` diff --git a/public/app/angular/panel/query_ctrl.ts b/public/app/angular/panel/query_ctrl.ts index 8f1bea4e6cb..1a02ba248bf 100644 --- a/public/app/angular/panel/query_ctrl.ts +++ b/public/app/angular/panel/query_ctrl.ts @@ -1,5 +1,5 @@ -import { indexOf } from 'lodash'; import { auto } from 'angular'; +import { indexOf } from 'lodash'; export class QueryCtrl { target!: T; diff --git a/public/app/angular/panel/specs/metrics_panel_ctrl.test.ts b/public/app/angular/panel/specs/metrics_panel_ctrl.test.ts index bbef5517f5f..12b281be6ff 100644 --- a/public/app/angular/panel/specs/metrics_panel_ctrl.test.ts +++ b/public/app/angular/panel/specs/metrics_panel_ctrl.test.ts @@ -18,6 +18,7 @@ jest.mock('app/core/config', () => { }); import { PanelModel } from 'app/features/dashboard/state/PanelModel'; + import { MetricsPanelCtrl } from '../metrics_panel_ctrl'; describe('MetricsPanelCtrl', () => { diff --git a/public/app/angular/promiseToDigest.test.ts b/public/app/angular/promiseToDigest.test.ts index e2961a7ac76..3bc81b36b31 100644 --- a/public/app/angular/promiseToDigest.test.ts +++ b/public/app/angular/promiseToDigest.test.ts @@ -1,4 +1,5 @@ import { IScope } from 'angular'; + import { promiseToDigest } from './promiseToDigest'; describe('promiseToDigest', () => { diff --git a/public/app/angular/rebuild_on_change.ts b/public/app/angular/rebuild_on_change.ts index 243aacfe0b6..33cb1c01a1b 100644 --- a/public/app/angular/rebuild_on_change.ts +++ b/public/app/angular/rebuild_on_change.ts @@ -1,4 +1,5 @@ import $ from 'jquery'; + import coreModule from './core_module'; function getBlockNodes(nodes: any[]) { diff --git a/public/app/angular/registerComponents.ts b/public/app/angular/registerComponents.ts index c0fda500ad5..d5a142b9d87 100644 --- a/public/app/angular/registerComponents.ts +++ b/public/app/angular/registerComponents.ts @@ -3,9 +3,10 @@ import { contextSrv } from 'app/core/core'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv'; import { getLinkSrv } from 'app/features/panel/panellinks/link_srv'; + import coreModule from './core_module'; -import { AnnotationsSrv } from './services/annotations_srv'; import { UtilSrv } from './services/UtilSrv'; +import { AnnotationsSrv } from './services/annotations_srv'; export function registerComponents() { coreModule.factory('backendSrv', () => getBackendSrv()); diff --git a/public/app/angular/services/AngularLoader.ts b/public/app/angular/services/AngularLoader.ts index 438da88cc88..b6449330c2e 100644 --- a/public/app/angular/services/AngularLoader.ts +++ b/public/app/angular/services/AngularLoader.ts @@ -1,9 +1,9 @@ import angular from 'angular'; -import coreModule from 'app/angular/core_module'; import { assign } from 'lodash'; import { AngularComponent, AngularLoader as AngularLoaderInterface } from '@grafana/runtime'; import { GrafanaRootScope } from 'app/angular/GrafanaCtrl'; +import coreModule from 'app/angular/core_module'; export class AngularLoader implements AngularLoaderInterface { /** @ngInject */ diff --git a/public/app/angular/services/UtilSrv.ts b/public/app/angular/services/UtilSrv.ts index 6d2a970aa27..af45b08cc7a 100644 --- a/public/app/angular/services/UtilSrv.ts +++ b/public/app/angular/services/UtilSrv.ts @@ -1,8 +1,9 @@ -import { GrafanaRootScope } from 'app/angular/GrafanaCtrl'; -import { HideModalEvent, ShowModalEvent } from '../../types/events'; import { deprecationWarning } from '@grafana/data'; +import { GrafanaRootScope } from 'app/angular/GrafanaCtrl'; import { appEvents } from 'app/core/app_events'; +import { HideModalEvent, ShowModalEvent } from '../../types/events'; + /** * Old legacy utilSrv exposed to angular services and handles angular modals. * Not used by any core or known external plugin. diff --git a/public/app/angular/services/annotations_srv.ts b/public/app/angular/services/annotations_srv.ts index 3e90ee3e594..711becaf505 100644 --- a/public/app/angular/services/annotations_srv.ts +++ b/public/app/angular/services/annotations_srv.ts @@ -1,6 +1,6 @@ import { cloneDeep } from 'lodash'; -import { AnnotationEvent, deprecationWarning } from '@grafana/data'; +import { AnnotationEvent, deprecationWarning } from '@grafana/data'; import { deleteAnnotation, saveAnnotation, updateAnnotation } from 'app/features/annotations/api'; import { AnnotationQueryOptions } from 'app/features/annotations/types'; diff --git a/public/app/angular/services/dynamic_directive_srv.ts b/public/app/angular/services/dynamic_directive_srv.ts index 30c4c860240..c85c232a72c 100644 --- a/public/app/angular/services/dynamic_directive_srv.ts +++ b/public/app/angular/services/dynamic_directive_srv.ts @@ -1,4 +1,5 @@ import angular from 'angular'; + import coreModule from '../core_module'; class DynamicDirectiveSrv { diff --git a/public/app/angular/services/nav_model_srv.ts b/public/app/angular/services/nav_model_srv.ts index f5431849cea..73506625130 100644 --- a/public/app/angular/services/nav_model_srv.ts +++ b/public/app/angular/services/nav_model_srv.ts @@ -1,6 +1,6 @@ +import { NavModel, NavModelItem } from '@grafana/data'; import coreModule from 'app/angular/core_module'; import config from 'app/core/config'; -import { NavModel, NavModelItem } from '@grafana/data'; interface Nav { breadcrumbs: NavModelItem[]; diff --git a/public/app/angular/services/ng_react.ts b/public/app/angular/services/ng_react.ts index 92871463bd9..8f33ea560cf 100644 --- a/public/app/angular/services/ng_react.ts +++ b/public/app/angular/services/ng_react.ts @@ -9,10 +9,10 @@ // - reactComponent (generic directive for delegating off to React Components) // - reactDirective (factory for creating specific directives that correspond to reactComponent directives) +import angular, { auto } from 'angular'; import { kebabCase } from 'lodash'; import React, { ComponentType } from 'react'; import ReactDOM from 'react-dom'; -import angular, { auto } from 'angular'; // get a react component from name (components can be an angular injectable e.g. value, factory or // available on window diff --git a/public/app/angular/services/popover_srv.ts b/public/app/angular/services/popover_srv.ts index e84c0975f33..1d96e118ccd 100644 --- a/public/app/angular/services/popover_srv.ts +++ b/public/app/angular/services/popover_srv.ts @@ -1,8 +1,9 @@ import { extend } from 'lodash'; -import coreModule from 'app/angular/core_module'; // @ts-ignore import Drop from 'tether-drop'; + import { GrafanaRootScope } from 'app/angular/GrafanaCtrl'; +import coreModule from 'app/angular/core_module'; /** @ngInject */ function popoverSrv(this: any, $compile: any, $rootScope: GrafanaRootScope, $timeout: any) { diff --git a/public/app/angular/services/segment_srv.ts b/public/app/angular/services/segment_srv.ts index b0c37eee45c..434593adc9c 100644 --- a/public/app/angular/services/segment_srv.ts +++ b/public/app/angular/services/segment_srv.ts @@ -1,4 +1,5 @@ import { each, isString, map } from 'lodash'; + import coreModule from '../core_module'; /** @ngInject */ diff --git a/public/app/angular/services/timer.ts b/public/app/angular/services/timer.ts index 177b3fc156c..8d12b8361cc 100644 --- a/public/app/angular/services/timer.ts +++ b/public/app/angular/services/timer.ts @@ -1,6 +1,7 @@ -import { without, each } from 'lodash'; -import coreModule from 'app/angular/core_module'; import { ITimeoutService } from 'angular'; +import { without, each } from 'lodash'; + +import coreModule from 'app/angular/core_module'; // This service really just tracks a list of $timeout promises to give us a // method for canceling them all when we need to diff --git a/public/app/angular/tags.ts b/public/app/angular/tags.ts index aad0db6e6e2..f5c840500e9 100644 --- a/public/app/angular/tags.ts +++ b/public/app/angular/tags.ts @@ -1,6 +1,8 @@ import angular from 'angular'; -import { getTagColorsFromName } from '@grafana/ui'; import $ from 'jquery'; + +import { getTagColorsFromName } from '@grafana/ui'; + import coreModule from './core_module'; import 'vendor/tagsinput/bootstrap-tagsinput.js'; diff --git a/public/app/app.ts b/public/app/app.ts index 1ca8c0435d7..f0b7ee0337e 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -7,12 +7,12 @@ import './polyfills/old-mediaquerylist'; // Safari < 14 does not have mql.addEve import 'file-saver'; import 'jquery'; -// eslint-disable-next-line lodash/import-scope -import _ from 'lodash'; -import ReactDOM from 'react-dom'; +import 'app/features/all'; + +import _ from 'lodash'; // eslint-disable-line lodash/import-scope import React from 'react'; -import config from 'app/core/config'; -// @ts-ignore ignoring this for now, otherwise we would have to extend _ interface with move +import ReactDOM from 'react-dom'; + import { locationUtil, monacoLanguageRegistry, @@ -23,8 +23,6 @@ import { standardFieldConfigEditorRegistry, standardTransformersRegistry, } from '@grafana/data'; -import { arrayMove } from 'app/core/utils/arrayMove'; -import { preloadPlugins } from './features/plugins/pluginPreloader'; import { locationService, registerEchoBackend, @@ -34,44 +32,48 @@ import { setLocationSrv, setQueryRunnerFactory, } from '@grafana/runtime'; +import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView'; +import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer'; +import { getScrollbarWidth } from '@grafana/ui'; +import config from 'app/core/config'; +import { arrayMove } from 'app/core/utils/arrayMove'; +import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; + +import getDefaultMonacoLanguages from '../lib/monaco-languages'; + +import { AppWrapper } from './AppWrapper'; +import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/components/editors/registry'; +import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks'; +import { ModalManager } from './core/services/ModalManager'; +import { backendSrv } from './core/services/backend_srv'; +import { contextSrv } from './core/services/context_srv'; import { Echo } from './core/services/echo/Echo'; import { reportPerformance } from './core/services/echo/EchoSrv'; import { PerformanceBackend } from './core/services/echo/backends/PerformanceBackend'; -import 'app/features/all'; -import { getScrollbarWidth } from '@grafana/ui'; -import { variableAdapters } from './features/variables/adapters'; -import { initDevFeatures } from './dev'; -import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; -import { SentryEchoBackend } from './core/services/echo/backends/sentry/SentryBackend'; -import { setVariableQueryRunner, VariableQueryRunner } from './features/variables/query/VariableQueryRunner'; -import { configureStore } from './store/configureStore'; -import { AppWrapper } from './AppWrapper'; -import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks'; -import { PanelRenderer } from './features/panel/components/PanelRenderer'; -import { QueryRunner } from './features/query/state/QueryRunner'; -import { getTimeSrv } from './features/dashboard/services/TimeSrv'; -import { getVariablesUrlParams } from './features/variables/getAllVariableValuesForUrl'; -import getDefaultMonacoLanguages from '../lib/monaco-languages'; -import { contextSrv } from './core/services/context_srv'; -import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend'; import { ApplicationInsightsBackend } from './core/services/echo/backends/analytics/ApplicationInsightsBackend'; +import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend'; import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend'; -import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/components/editors/registry'; -import { backendSrv } from './core/services/backend_srv'; -import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer'; +import { SentryEchoBackend } from './core/services/echo/backends/sentry/SentryBackend'; +import { initDevFeatures } from './dev'; +import { getTimeSrv } from './features/dashboard/services/TimeSrv'; import { PanelDataErrorView } from './features/panel/components/PanelDataErrorView'; -import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView'; +import { PanelRenderer } from './features/panel/components/PanelRenderer'; import { DatasourceSrv } from './features/plugins/datasource_srv'; -import { ModalManager } from './core/services/ModalManager'; +import { preloadPlugins } from './features/plugins/pluginPreloader'; +import { QueryRunner } from './features/query/state/QueryRunner'; import { initWindowRuntime } from './features/runtime/init'; -import { createQueryVariableAdapter } from './features/variables/query/adapter'; -import { createCustomVariableAdapter } from './features/variables/custom/adapter'; -import { createTextBoxVariableAdapter } from './features/variables/textbox/adapter'; -import { createConstantVariableAdapter } from './features/variables/constant/adapter'; -import { createDataSourceVariableAdapter } from './features/variables/datasource/adapter'; -import { createIntervalVariableAdapter } from './features/variables/interval/adapter'; +import { variableAdapters } from './features/variables/adapters'; import { createAdHocVariableAdapter } from './features/variables/adhoc/adapter'; +import { createConstantVariableAdapter } from './features/variables/constant/adapter'; +import { createCustomVariableAdapter } from './features/variables/custom/adapter'; +import { createDataSourceVariableAdapter } from './features/variables/datasource/adapter'; +import { getVariablesUrlParams } from './features/variables/getAllVariableValuesForUrl'; +import { createIntervalVariableAdapter } from './features/variables/interval/adapter'; +import { setVariableQueryRunner, VariableQueryRunner } from './features/variables/query/VariableQueryRunner'; +import { createQueryVariableAdapter } from './features/variables/query/adapter'; import { createSystemVariableAdapter } from './features/variables/system/adapter'; +import { createTextBoxVariableAdapter } from './features/variables/textbox/adapter'; +import { configureStore } from './store/configureStore'; // add move to lodash for backward compatabilty with plugins // @ts-ignore diff --git a/public/app/core/actions/index.ts b/public/app/core/actions/index.ts index 4df4144e94c..88c6c68ab2e 100644 --- a/public/app/core/actions/index.ts +++ b/public/app/core/actions/index.ts @@ -1,3 +1,3 @@ -import { clearAppNotification, notifyApp } from '../reducers/appNotification'; +import { hideAppNotification, notifyApp } from '../reducers/appNotification'; import { updateNavIndex, updateConfigurationSubtitle } from '../reducers/navModel'; -export { updateNavIndex, updateConfigurationSubtitle, notifyApp, clearAppNotification }; +export { updateNavIndex, updateConfigurationSubtitle, notifyApp, hideAppNotification }; diff --git a/public/app/core/components/AccessControl/AddPermission.tsx b/public/app/core/components/AccessControl/AddPermission.tsx index aef87a20de6..48ecd803555 100644 --- a/public/app/core/components/AccessControl/AddPermission.tsx +++ b/public/app/core/components/AccessControl/AddPermission.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { UserPicker } from 'app/core/components/Select/UserPicker'; -import { TeamPicker } from 'app/core/components/Select/TeamPicker'; + import { Alert, Button, Form, HorizontalGroup, Input, Select } from '@grafana/ui'; -import { OrgRole } from 'app/types/acl'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import { TeamPicker } from 'app/core/components/Select/TeamPicker'; +import { UserPicker } from 'app/core/components/Select/UserPicker'; +import { OrgRole } from 'app/types/acl'; + import { Assignments, PermissionTarget, SetPermission } from './types'; export interface Props { diff --git a/public/app/core/components/AccessControl/PermissionList.tsx b/public/app/core/components/AccessControl/PermissionList.tsx index 9b1fb961235..2deed480de7 100644 --- a/public/app/core/components/AccessControl/PermissionList.tsx +++ b/public/app/core/components/AccessControl/PermissionList.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { ResourcePermission } from './types'; + import { PermissionListItem } from './PermissionListItem'; +import { ResourcePermission } from './types'; interface Props { title: string; diff --git a/public/app/core/components/AccessControl/PermissionListItem.tsx b/public/app/core/components/AccessControl/PermissionListItem.tsx index ca88d08a5d8..6744c7ca167 100644 --- a/public/app/core/components/AccessControl/PermissionListItem.tsx +++ b/public/app/core/components/AccessControl/PermissionListItem.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { ResourcePermission } from './types'; + import { Button, Icon, Select, Tooltip } from '@grafana/ui'; +import { ResourcePermission } from './types'; + interface Props { item: ResourcePermission; permissionLevels: string[]; diff --git a/public/app/core/components/AccessControl/Permissions.tsx b/public/app/core/components/AccessControl/Permissions.tsx index b8dd561382d..b7fb44aaefa 100644 --- a/public/app/core/components/AccessControl/Permissions.tsx +++ b/public/app/core/components/AccessControl/Permissions.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { sortBy } from 'lodash'; -import { getBackendSrv } from 'app/core/services/backend_srv'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; +import { getBackendSrv } from 'app/core/services/backend_srv'; + import { AddPermission } from './AddPermission'; import { PermissionList } from './PermissionList'; import { PermissionTarget, ResourcePermission, SetPermission, Description } from './types'; diff --git a/public/app/core/components/AppNotifications/AppNotificationItem.tsx b/public/app/core/components/AppNotifications/AppNotificationItem.tsx index c1c227f4fd4..3ec1a2b59a2 100644 --- a/public/app/core/components/AppNotifications/AppNotificationItem.tsx +++ b/public/app/core/components/AppNotifications/AppNotificationItem.tsx @@ -1,36 +1,48 @@ -import React, { Component } from 'react'; -import { AppNotification } from 'app/types'; -import { Alert } from '@grafana/ui'; +import { css } from '@emotion/css'; +import React from 'react'; +import { useEffectOnce } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, useStyles2 } from '@grafana/ui'; +import { AppNotification, timeoutMap } from 'app/types'; interface Props { appNotification: AppNotification; onClearNotification: (id: string) => void; } -export default class AppNotificationItem extends Component { - shouldComponentUpdate(nextProps: Props) { - return this.props.appNotification.id !== nextProps.appNotification.id; - } +export default function AppNotificationItem({ appNotification, onClearNotification }: Props) { + const styles = useStyles2(getStyles); - componentDidMount() { - const { appNotification, onClearNotification } = this.props; + useEffectOnce(() => { setTimeout(() => { onClearNotification(appNotification.id); - }, appNotification.timeout); - } + }, timeoutMap[appNotification.severity]); + }); - render() { - const { appNotification, onClearNotification } = this.props; + return ( + onClearNotification(appNotification.id)} + elevated + > +
+ {appNotification.component || appNotification.text} + {appNotification.traceId && Trace ID: {appNotification.traceId}} +
+
+ ); +} - return ( - onClearNotification(appNotification.id)} - elevated - > - {appNotification.component || appNotification.text} - - ); - } +function getStyles(theme: GrafanaTheme2) { + return { + wrapper: css({ + display: 'flex', + flexDirection: 'column', + }), + trace: css({ + fontSize: theme.typography.pxToRem(10), + }), + }; } diff --git a/public/app/core/components/AppNotifications/AppNotificationList.tsx b/public/app/core/components/AppNotifications/AppNotificationList.tsx index 4fe53b56801..82723c43cb6 100644 --- a/public/app/core/components/AppNotifications/AppNotificationList.tsx +++ b/public/app/core/components/AppNotifications/AppNotificationList.tsx @@ -1,8 +1,11 @@ import React, { PureComponent } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; + +import { AppEvents } from '@grafana/data'; +import { VerticalGroup } from '@grafana/ui'; +import { notifyApp, hideAppNotification } from 'app/core/actions'; import appEvents from 'app/core/app_events'; -import AppNotificationItem from './AppNotificationItem'; -import { notifyApp, clearAppNotification } from 'app/core/actions'; -import { selectAll } from 'app/core/reducers/appNotification'; +import { selectVisible } from 'app/core/reducers/appNotification'; import { StoreState } from 'app/types'; import { @@ -10,19 +13,18 @@ import { createSuccessNotification, createWarningNotification, } from '../../copy/appNotification'; -import { AppEvents } from '@grafana/data'; -import { connect, ConnectedProps } from 'react-redux'; -import { VerticalGroup } from '@grafana/ui'; + +import AppNotificationItem from './AppNotificationItem'; export interface OwnProps {} const mapStateToProps = (state: StoreState, props: OwnProps) => ({ - appNotifications: selectAll(state.appNotifications), + appNotifications: selectVisible(state.appNotifications), }); const mapDispatchToProps = { notifyApp, - clearAppNotification, + hideAppNotification, }; const connector = connect(mapStateToProps, mapDispatchToProps); @@ -39,7 +41,7 @@ export class AppNotificationListUnConnected extends PureComponent { } onClearAppNotification = (id: string) => { - this.props.clearAppNotification(id); + this.props.hideAppNotification(id); }; render() { diff --git a/public/app/core/components/AppNotifications/StoredNotificationItem.tsx b/public/app/core/components/AppNotifications/StoredNotificationItem.tsx new file mode 100644 index 00000000000..d6fe2992b5a --- /dev/null +++ b/public/app/core/components/AppNotifications/StoredNotificationItem.tsx @@ -0,0 +1,112 @@ +import { css } from '@emotion/css'; +import { formatDistanceToNow } from 'date-fns'; +import React, { ReactNode } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, IconButton, IconName, useTheme2 } from '@grafana/ui'; +import { getIconFromSeverity } from '@grafana/ui/src/components/Alert/Alert'; + +export type AlertVariant = 'success' | 'warning' | 'error' | 'info'; + +export interface Props { + title: string; + severity?: AlertVariant; + timestamp?: number; + traceId?: string; + children?: ReactNode; + onRemove?: (event: React.MouseEvent) => void; +} + +export const StoredNotificationItem = ({ + title, + severity = 'error', + traceId, + timestamp, + children, + onRemove, +}: Props) => { + const theme = useTheme2(); + const styles = getStyles(theme, severity); + + return ( +
+
+ +
+
{title}
+
{children}
+ {traceId && `Trace ID: ${traceId}`} +
+ +
+ {timestamp && {formatDistanceToNow(timestamp, { addSuffix: true })}} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2, severity: AlertVariant) => { + const color = theme.colors[severity]; + const borderRadius = theme.shape.borderRadius(); + + return { + wrapper: css({ + display: 'grid', + gridTemplateColumns: 'auto 1fr auto', + gridTemplateRows: 'auto 1fr auto', + gridTemplateAreas: ` + 'icon title close' + 'icon body body' + 'icon trace timestamp'`, + gap: `0 ${theme.spacing(2)}`, + background: theme.colors.background.secondary, + borderRadius: borderRadius, + }), + icon: css({ + gridArea: 'icon', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: theme.spacing(2, 3), + background: color.main, + color: color.contrastText, + borderRadius: `${borderRadius} 0 0 ${borderRadius}`, + }), + title: css({ + gridArea: 'title', + alignSelf: 'center', + fontWeight: theme.typography.fontWeightMedium, + color: theme.colors.text.primary, + }), + body: css({ + gridArea: 'body', + maxHeight: '50vh', + marginRight: theme.spacing(1), + overflowY: 'auto', + overflowWrap: 'break-word', + wordBreak: 'break-word', + color: theme.colors.text.secondary, + }), + trace: css({ + gridArea: 'trace', + justifySelf: 'start', + alignSelf: 'end', + paddingBottom: theme.spacing(1), + fontSize: theme.typography.pxToRem(10), + color: theme.colors.text.secondary, + }), + close: css({ + gridArea: 'close', + display: 'flex', + justifySelf: 'end', + padding: theme.spacing(1, 0.5), + background: 'none', + }), + timestamp: css({ + gridArea: 'timestamp', + alignSelf: 'end', + padding: theme.spacing(1), + fontSize: theme.typography.pxToRem(10), + color: theme.colors.text.secondary, + }), + }; +}; diff --git a/public/app/core/components/Branding/Branding.tsx b/public/app/core/components/Branding/Branding.tsx index 15035264f15..4968330dd59 100644 --- a/public/app/core/components/Branding/Branding.tsx +++ b/public/app/core/components/Branding/Branding.tsx @@ -1,7 +1,8 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; -import { useTheme2, styleMixins } from '@grafana/ui'; +import React, { FC } from 'react'; + import { colorManipulator } from '@grafana/data'; +import { useTheme2, styleMixins } from '@grafana/ui'; export interface BrandComponentProps { className?: string; diff --git a/public/app/core/components/CardButton.tsx b/public/app/core/components/CardButton.tsx index 45d87b600c4..d8a73799e12 100644 --- a/public/app/core/components/CardButton.tsx +++ b/public/app/core/components/CardButton.tsx @@ -1,5 +1,6 @@ -import React, { HTMLAttributes } from 'react'; import { css } from '@emotion/css'; +import React, { HTMLAttributes } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Icon, IconName, useStyles2 } from '@grafana/ui'; diff --git a/public/app/core/components/CloseButton/CloseButton.tsx b/public/app/core/components/CloseButton/CloseButton.tsx index d9cd78f1783..1a6d000a12b 100644 --- a/public/app/core/components/CloseButton/CloseButton.tsx +++ b/public/app/core/components/CloseButton/CloseButton.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { IconButton, useStyles2 } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { IconButton, useStyles2 } from '@grafana/ui'; type Props = { onClick: () => void; diff --git a/public/app/core/components/ColorScale/ColorScale.tsx b/public/app/core/components/ColorScale/ColorScale.tsx index 31080de26bd..ba9ece2581d 100644 --- a/public/app/core/components/ColorScale/ColorScale.tsx +++ b/public/app/core/components/ColorScale/ColorScale.tsx @@ -1,5 +1,6 @@ -import React, { useState, useEffect } from 'react'; import { css } from '@emotion/css'; +import React, { useState, useEffect } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useTheme2 } from '@grafana/ui'; diff --git a/public/app/core/components/DynamicImports/ErrorLoadingChunk.tsx b/public/app/core/components/DynamicImports/ErrorLoadingChunk.tsx index a235c9a415d..927ce787251 100644 --- a/public/app/core/components/DynamicImports/ErrorLoadingChunk.tsx +++ b/public/app/core/components/DynamicImports/ErrorLoadingChunk.tsx @@ -1,6 +1,7 @@ -import React, { FunctionComponent } from 'react'; -import { Button, stylesFactory } from '@grafana/ui'; import { css } from '@emotion/css'; +import React, { FunctionComponent } from 'react'; + +import { Button, stylesFactory } from '@grafana/ui'; import { useUrlParams } from 'app/core/navigation/hooks'; const getStyles = stylesFactory(() => { diff --git a/public/app/core/components/DynamicImports/LoadingChunkPlaceHolder.tsx b/public/app/core/components/DynamicImports/LoadingChunkPlaceHolder.tsx index a5813a03252..220310882a2 100644 --- a/public/app/core/components/DynamicImports/LoadingChunkPlaceHolder.tsx +++ b/public/app/core/components/DynamicImports/LoadingChunkPlaceHolder.tsx @@ -1,4 +1,5 @@ import React, { FunctionComponent } from 'react'; + import { LoadingPlaceholder } from '@grafana/ui'; export const LoadingChunkPlaceHolder: FunctionComponent = React.memo(() => ( diff --git a/public/app/core/components/DynamicImports/SafeDynamicImport.test.tsx b/public/app/core/components/DynamicImports/SafeDynamicImport.test.tsx index 2b4a99d0f2e..6cd3ffd23ad 100644 --- a/public/app/core/components/DynamicImports/SafeDynamicImport.test.tsx +++ b/public/app/core/components/DynamicImports/SafeDynamicImport.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { loadComponentHandler } from './SafeDynamicImport'; + import { ErrorLoadingChunk } from './ErrorLoadingChunk'; import { LoadingChunkPlaceHolder } from './LoadingChunkPlaceHolder'; +import { loadComponentHandler } from './SafeDynamicImport'; describe('loadComponentHandler', () => { describe('when there is no error and pastDelay is false', () => { diff --git a/public/app/core/components/DynamicImports/SafeDynamicImport.tsx b/public/app/core/components/DynamicImports/SafeDynamicImport.tsx index 4544d88a5f1..193ed6712c4 100644 --- a/public/app/core/components/DynamicImports/SafeDynamicImport.tsx +++ b/public/app/core/components/DynamicImports/SafeDynamicImport.tsx @@ -1,9 +1,11 @@ import React from 'react'; import Loadable from 'react-loadable'; -import { LoadingChunkPlaceHolder } from './LoadingChunkPlaceHolder'; -import { ErrorLoadingChunk } from './ErrorLoadingChunk'; + import { GrafanaRouteComponent } from 'app/core/navigation/types'; +import { ErrorLoadingChunk } from './ErrorLoadingChunk'; +import { LoadingChunkPlaceHolder } from './LoadingChunkPlaceHolder'; + export const loadComponentHandler = (props: { error: Error; pastDelay: boolean }) => { const { error, pastDelay } = props; diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx index 9dd81f6c69d..40ca25574b7 100644 --- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; +import React, { MouseEvent } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; import { Button, CallToActionCard, Icon, IconName, LinkButton } from '@grafana/ui'; -import React, { MouseEvent } from 'react'; export interface Props { title: string; diff --git a/public/app/core/components/ErrorPage/ErrorPage.tsx b/public/app/core/components/ErrorPage/ErrorPage.tsx index d12637698de..8fe3e4c645e 100644 --- a/public/app/core/components/ErrorPage/ErrorPage.tsx +++ b/public/app/core/components/ErrorPage/ErrorPage.tsx @@ -1,12 +1,14 @@ import React, { PureComponent } from 'react'; import { connect, MapStateToProps } from 'react-redux'; + import { NavModel } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Icon } from '@grafana/ui'; -import Page from '../Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; +import Page from '../Page/Page'; + interface ConnectedProps { navModel: NavModel; } diff --git a/public/app/core/components/FolderFilter/FolderFilter.tsx b/public/app/core/components/FolderFilter/FolderFilter.tsx index 8d149451713..795b3af7990 100644 --- a/public/app/core/components/FolderFilter/FolderFilter.tsx +++ b/public/app/core/components/FolderFilter/FolderFilter.tsx @@ -1,11 +1,11 @@ -import React, { useCallback, useMemo, useState } from 'react'; import { css } from '@emotion/css'; import debounce from 'debounce-promise'; -import { AsyncMultiSelect, Icon, Button, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import React, { useCallback, useMemo, useState } from 'react'; -import { FolderInfo, PermissionLevelString } from 'app/types'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { AsyncMultiSelect, Icon, Button, useStyles2 } from '@grafana/ui'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { FolderInfo, PermissionLevelString } from 'app/types'; export interface FolderFilterProps { onChange: (folder: FolderInfo[]) => void; diff --git a/public/app/core/components/Footer/Footer.tsx b/public/app/core/components/Footer/Footer.tsx index b3c045e2a37..4fa437b7913 100644 --- a/public/app/core/components/Footer/Footer.tsx +++ b/public/app/core/components/Footer/Footer.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { config } from '@grafana/runtime'; import { Icon, IconName } from '@grafana/ui'; diff --git a/public/app/core/components/ForgottenPassword/ChangePassword.tsx b/public/app/core/components/ForgottenPassword/ChangePassword.tsx index 69c813b4a50..3a014973cb4 100644 --- a/public/app/core/components/ForgottenPassword/ChangePassword.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePassword.tsx @@ -1,6 +1,8 @@ import React, { FC, SyntheticEvent } from 'react'; -import { Tooltip, Form, Field, VerticalGroup, Button } from '@grafana/ui'; + import { selectors } from '@grafana/e2e-selectors'; +import { Tooltip, Form, Field, VerticalGroup, Button } from '@grafana/ui'; + import { submitButton } from '../Login/LoginForm'; import { PasswordField } from '../PasswordField/PasswordField'; interface Props { diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx index 5021e7cf081..6df0b66cefb 100644 --- a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { ChangePasswordPage, Props } from './ChangePasswordPage'; @@ -48,11 +49,11 @@ describe('ChangePassword Page', () => { expect(await screen.findByText('New Password is required')).toBeInTheDocument(); expect(screen.getByText('Confirmed Password is required')).toBeInTheDocument(); - userEvent.type(screen.getByLabelText('New password'), 'admin'); - userEvent.type(screen.getByLabelText('Confirm new password'), 'a'); + await userEvent.type(screen.getByLabelText('New password'), 'admin'); + await userEvent.type(screen.getByLabelText('Confirm new password'), 'a'); await waitFor(() => expect(screen.getByText('Passwords must match!')).toBeInTheDocument()); - userEvent.type(screen.getByLabelText('Confirm new password'), 'dmin'); + await userEvent.type(screen.getByLabelText('Confirm new password'), 'dmin'); await waitFor(() => expect(screen.queryByText('Passwords must match!')).not.toBeInTheDocument()); }); it('should navigate to default url if change password is successful', async () => { @@ -64,8 +65,8 @@ describe('ChangePassword Page', () => { postMock.mockResolvedValueOnce({ message: 'Logged in' }); render(); - userEvent.type(screen.getByLabelText('New password'), 'test'); - userEvent.type(screen.getByLabelText('Confirm new password'), 'test'); + await userEvent.type(screen.getByLabelText('New password'), 'test'); + await userEvent.type(screen.getByLabelText('Confirm new password'), 'test'); fireEvent.click(screen.getByRole('button', { name: 'Submit' })); await waitFor(() => expect(postMock).toHaveBeenCalledWith('/api/user/password/reset', { diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx index 55bb5da34f3..70fe1ab4c06 100644 --- a/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx @@ -1,9 +1,12 @@ import React, { FC } from 'react'; -import { LoginLayout, InnerBox } from '../Login/LoginLayout'; -import { ChangePassword } from './ChangePassword'; -import LoginCtrl from '../Login/LoginCtrl'; + import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import LoginCtrl from '../Login/LoginCtrl'; +import { LoginLayout, InnerBox } from '../Login/LoginLayout'; + +import { ChangePassword } from './ChangePassword'; + export interface Props extends GrafanaRouteComponentProps<{}, { code: string }> {} export const ChangePasswordPage: FC = (props) => { diff --git a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx index 94b4f0b2ea8..c375b9bcff1 100644 --- a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx +++ b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx @@ -1,8 +1,9 @@ -import React, { FC, useState } from 'react'; -import { Form, Field, Input, Button, Legend, Container, useStyles, HorizontalGroup, LinkButton } from '@grafana/ui'; -import { getBackendSrv } from '@grafana/runtime'; import { css } from '@emotion/css'; +import React, { FC, useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; +import { Form, Field, Input, Button, Legend, Container, useStyles, HorizontalGroup, LinkButton } from '@grafana/ui'; import config from 'app/core/config'; interface EmailDTO { diff --git a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx index f9bb024ae40..57da0cd1029 100644 --- a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { SendResetMailPage } from './SendResetMailPage'; @@ -41,14 +41,14 @@ describe('VerifyEmail Page', () => { fireEvent.click(screen.getByRole('button', { name: 'Send reset email' })); expect(await screen.findByText('Email or username is required')).toBeInTheDocument(); - userEvent.type(screen.getByRole('textbox', { name: /User Enter your information/i }), 'test@gmail.com'); + await userEvent.type(screen.getByRole('textbox', { name: /User Enter your information/i }), 'test@gmail.com'); await waitFor(() => expect(screen.queryByText('Email is invalid')).not.toBeInTheDocument()); }); it('should show success meessage if reset-password is successful', async () => { postMock.mockResolvedValueOnce({ message: 'Email sent' }); render(); - userEvent.type(screen.getByRole('textbox', { name: /User Enter your information/i }), 'test@gmail.com'); + await userEvent.type(screen.getByRole('textbox', { name: /User Enter your information/i }), 'test@gmail.com'); fireEvent.click(screen.getByRole('button', { name: 'Send reset email' })); await waitFor(() => expect(postMock).toHaveBeenCalledWith('/api/user/password/send-reset-email', { diff --git a/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx b/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx index 1ab5a36ff35..8c0e4797884 100644 --- a/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx +++ b/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; import { LoginLayout, InnerBox } from '../Login/LoginLayout'; + import { ForgottenPassword } from './ForgottenPassword'; export const SendResetMailPage: FC = () => ( diff --git a/public/app/core/components/Layers/AddLayerButton.tsx b/public/app/core/components/Layers/AddLayerButton.tsx index 272735285ad..7bf53be8ba6 100644 --- a/public/app/core/components/Layers/AddLayerButton.tsx +++ b/public/app/core/components/Layers/AddLayerButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { ValuePicker } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; +import { ValuePicker } from '@grafana/ui'; type AddLayerButtonProps = { onChange: (sel: SelectableValue) => void; diff --git a/public/app/core/components/Layers/LayerDragDropList.tsx b/public/app/core/components/Layers/LayerDragDropList.tsx index 282ed0be587..d4ceae1dc90 100644 --- a/public/app/core/components/Layers/LayerDragDropList.tsx +++ b/public/app/core/components/Layers/LayerDragDropList.tsx @@ -1,9 +1,10 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; -import { css, cx } from '@emotion/css'; -import { Icon, IconButton, stylesFactory } from '@grafana/ui'; + import { GrafanaTheme } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { Icon, IconButton, stylesFactory } from '@grafana/ui'; import { LayerName } from './LayerName'; import { LayerElement } from './types'; diff --git a/public/app/core/components/Layers/LayerName.test.tsx b/public/app/core/components/Layers/LayerName.test.tsx index 0fda27cf1ad..bd367d36174 100644 --- a/public/app/core/components/Layers/LayerName.test.tsx +++ b/public/app/core/components/Layers/LayerName.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { LayerNameProps, LayerName } from './LayerName'; describe('LayerName', () => { diff --git a/public/app/core/components/Layers/LayerName.tsx b/public/app/core/components/Layers/LayerName.tsx index c229d7f0099..dbf1c42bb1b 100644 --- a/public/app/core/components/Layers/LayerName.tsx +++ b/public/app/core/components/Layers/LayerName.tsx @@ -1,7 +1,8 @@ -import React, { useState } from 'react'; import { css, cx } from '@emotion/css'; -import { Icon, Input, FieldValidationMessage, useStyles } from '@grafana/ui'; +import React, { useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { Icon, Input, FieldValidationMessage, useStyles } from '@grafana/ui'; export interface LayerNameProps { name: string; diff --git a/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx b/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx index c8923f3e1f6..621405329e8 100644 --- a/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx +++ b/public/app/core/components/LocalStorageValueProvider/LocalStorageValueProvider.tsx @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; + import store from '../../store'; export interface Props { diff --git a/public/app/core/components/Login/LoginCtrl.tsx b/public/app/core/components/Login/LoginCtrl.tsx index aa969a8d729..c7277237833 100644 --- a/public/app/core/components/Login/LoginCtrl.tsx +++ b/public/app/core/components/Login/LoginCtrl.tsx @@ -1,8 +1,9 @@ import React, { PureComponent } from 'react'; -import config from 'app/core/config'; + +import { AppEvents } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; -import { AppEvents } from '@grafana/data'; +import config from 'app/core/config'; const isOauthEnabled = () => { return !!config.oauth && Object.keys(config.oauth).length > 0; diff --git a/public/app/core/components/Login/LoginForm.tsx b/public/app/core/components/Login/LoginForm.tsx index e7dc1d4d589..9364c5a0c13 100644 --- a/public/app/core/components/Login/LoginForm.tsx +++ b/public/app/core/components/Login/LoginForm.tsx @@ -1,10 +1,12 @@ +import { css } from '@emotion/css'; import React, { FC, ReactElement } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; +import { Button, Form, Input, Field } from '@grafana/ui'; + +import { PasswordField } from '../PasswordField/PasswordField'; import { FormModel } from './LoginCtrl'; -import { Button, Form, Input, Field } from '@grafana/ui'; -import { css } from '@emotion/css'; -import { PasswordField } from '../PasswordField/PasswordField'; interface Props { children: ReactElement; diff --git a/public/app/core/components/Login/LoginLayout.tsx b/public/app/core/components/Login/LoginLayout.tsx index c2e5f44821d..5380f7b49c7 100644 --- a/public/app/core/components/Login/LoginLayout.tsx +++ b/public/app/core/components/Login/LoginLayout.tsx @@ -1,8 +1,10 @@ -import React, { FC, useEffect, useState } from 'react'; import { cx, css, keyframes } from '@emotion/css'; -import { useStyles2, styleMixins } from '@grafana/ui'; -import { Branding } from '../Branding/Branding'; +import React, { FC, useEffect, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2, styleMixins } from '@grafana/ui'; + +import { Branding } from '../Branding/Branding'; import { Footer } from '../Footer/Footer'; interface InnerBoxProps { diff --git a/public/app/core/components/Login/LoginPage.test.tsx b/public/app/core/components/Login/LoginPage.test.tsx index 3d5161182df..dbbf6ec4c9e 100644 --- a/public/app/core/components/Login/LoginPage.test.tsx +++ b/public/app/core/components/Login/LoginPage.test.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { LoginPage } from './LoginPage'; +import React from 'react'; import * as runtimeMock from '@grafana/runtime'; +import { LoginPage } from './LoginPage'; + const postMock = jest.fn(); jest.mock('@grafana/runtime', () => ({ __esModule: true, @@ -56,7 +57,7 @@ describe('Login Page', () => { fireEvent.click(screen.getByRole('button', { name: 'Login button' })); expect(await screen.findByText('Email or username is required')).toBeInTheDocument(); - userEvent.type(screen.getByRole('textbox', { name: 'Username input field' }), 'admin'); + await userEvent.type(screen.getByRole('textbox', { name: 'Username input field' }), 'admin'); await waitFor(() => expect(screen.queryByText('Email or username is required')).not.toBeInTheDocument()); }); it('should pass validation checks for password field', async () => { @@ -65,7 +66,7 @@ describe('Login Page', () => { fireEvent.click(screen.getByRole('button', { name: 'Login button' })); expect(await screen.findByText('Password is required')).toBeInTheDocument(); - userEvent.type(screen.getByLabelText('Password input field'), 'admin'); + await userEvent.type(screen.getByLabelText('Password input field'), 'admin'); await waitFor(() => expect(screen.queryByText('Password is required')).not.toBeInTheDocument()); }); it('should navigate to default url if credentials is valid', async () => { @@ -77,8 +78,8 @@ describe('Login Page', () => { postMock.mockResolvedValueOnce({ message: 'Logged in' }); render(); - userEvent.type(screen.getByLabelText('Username input field'), 'admin'); - userEvent.type(screen.getByLabelText('Password input field'), 'test'); + await userEvent.type(screen.getByLabelText('Username input field'), 'admin'); + await userEvent.type(screen.getByLabelText('Password input field'), 'test'); fireEvent.click(screen.getByLabelText('Login button')); await waitFor(() => expect(postMock).toHaveBeenCalledWith('/login', { password: 'test', user: 'admin' })); diff --git a/public/app/core/components/Login/LoginPage.tsx b/public/app/core/components/Login/LoginPage.tsx index 581e248980e..bb49378ce60 100644 --- a/public/app/core/components/Login/LoginPage.tsx +++ b/public/app/core/components/Login/LoginPage.tsx @@ -1,17 +1,19 @@ // Libraries -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; // Components -import { UserSignup } from './UserSignup'; -import { LoginServiceButtons } from './LoginServiceButtons'; +import { HorizontalGroup, LinkButton } from '@grafana/ui'; +import { Branding } from 'app/core/components/Branding/Branding'; +import config from 'app/core/config'; + +import { ChangePassword } from '../ForgottenPassword/ChangePassword'; + import LoginCtrl from './LoginCtrl'; import { LoginForm } from './LoginForm'; -import { ChangePassword } from '../ForgottenPassword/ChangePassword'; -import { Branding } from 'app/core/components/Branding/Branding'; -import { HorizontalGroup, LinkButton } from '@grafana/ui'; import { LoginLayout, InnerBox } from './LoginLayout'; -import config from 'app/core/config'; +import { LoginServiceButtons } from './LoginServiceButtons'; +import { UserSignup } from './UserSignup'; const forgottenPasswordStyles = css` padding: 0; diff --git a/public/app/core/components/Login/LoginServiceButtons.tsx b/public/app/core/components/Login/LoginServiceButtons.tsx index ab8a648aad7..69571d7241d 100644 --- a/public/app/core/components/Login/LoginServiceButtons.tsx +++ b/public/app/core/components/Login/LoginServiceButtons.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import config from 'app/core/config'; import { css, cx } from '@emotion/css'; -import { Icon, IconName, LinkButton, useStyles, useTheme2, VerticalGroup } from '@grafana/ui'; -import { GrafanaTheme, GrafanaTheme2, DEFAULT_SAML_NAME } from '@grafana/data'; import { pickBy } from 'lodash'; +import React from 'react'; + +import { GrafanaTheme, GrafanaTheme2, DEFAULT_SAML_NAME } from '@grafana/data'; +import { Icon, IconName, LinkButton, useStyles, useTheme2, VerticalGroup } from '@grafana/ui'; +import config from 'app/core/config'; export interface LoginService { bgColor: string; diff --git a/public/app/core/components/Login/UserSignup.tsx b/public/app/core/components/Login/UserSignup.tsx index 3f80b49b1fa..56113665b6d 100644 --- a/public/app/core/components/Login/UserSignup.tsx +++ b/public/app/core/components/Login/UserSignup.tsx @@ -1,6 +1,7 @@ -import React, { FC } from 'react'; -import { LinkButton, VerticalGroup } from '@grafana/ui'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { LinkButton, VerticalGroup } from '@grafana/ui'; import { getConfig } from 'app/core/config'; export const UserSignup: FC<{}> = () => { diff --git a/public/app/core/components/NavBar/NavBar.test.tsx b/public/app/core/components/NavBar/NavBar.test.tsx index 94b89e36b35..39e2949a651 100644 --- a/public/app/core/components/NavBar/NavBar.test.tsx +++ b/public/app/core/components/NavBar/NavBar.test.tsx @@ -1,10 +1,13 @@ +import { render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; + import { locationService } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; + import TestProvider from '../../../../test/helpers/TestProvider'; + import { NavBar } from './NavBar'; jest.mock('app/core/services/context_srv', () => ({ diff --git a/public/app/core/components/NavBar/NavBar.tsx b/public/app/core/components/NavBar/NavBar.tsx index a2c155df816..deb4a58b24e 100644 --- a/public/app/core/components/NavBar/NavBar.tsx +++ b/public/app/core/components/NavBar/NavBar.tsx @@ -1,21 +1,24 @@ +import { css, cx } from '@emotion/css'; +import { cloneDeep } from 'lodash'; import React, { useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { css, cx } from '@emotion/css'; -import { cloneDeep } from 'lodash'; + import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data'; -import { Icon, IconName, useTheme2 } from '@grafana/ui'; import { locationService } from '@grafana/runtime'; +import { Icon, IconName, useTheme2 } from '@grafana/ui'; import { Branding } from 'app/core/components/Branding/Branding'; import config from 'app/core/config'; import { getKioskMode } from 'app/core/navigation/kiosk'; import { KioskMode, StoreState } from 'app/types'; -import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive, SEARCH_ITEM_ID } from './utils'; + import { OrgSwitcher } from '../OrgSwitcher'; + import NavBarItem from './NavBarItem'; -import { NavBarSection } from './NavBarSection'; -import { NavBarMenu } from './NavBarMenu'; import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; +import { NavBarMenu } from './NavBarMenu'; +import { NavBarSection } from './NavBarSection'; +import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive, SEARCH_ITEM_ID } from './utils'; const homeUrl = config.appSubUrl || '/'; diff --git a/public/app/core/components/NavBar/NavBarItem.test.tsx b/public/app/core/components/NavBar/NavBarItem.test.tsx index 9425298a4ab..1f72a08b4ee 100644 --- a/public/app/core/components/NavBar/NavBarItem.test.tsx +++ b/public/app/core/components/NavBar/NavBarItem.test.tsx @@ -1,11 +1,23 @@ -import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { BrowserRouter } from 'react-router-dom'; + import { locationUtil } from '@grafana/data'; import { config, setLocationService } from '@grafana/runtime'; + import TestProvider from '../../../../test/helpers/TestProvider'; +// Need to mock createBrowserHistory here to avoid errors +jest.mock('history', () => ({ + ...jest.requireActual('history'), + createBrowserHistory: () => ({ + listen: jest.fn(), + location: {}, + createHref: jest.fn(), + }), +})); + import NavBarItem, { Props } from './NavBarItem'; const onClickMock = jest.fn(); @@ -21,7 +33,7 @@ const defaults: Props = { }, }; -function getTestContext(overrides: Partial = {}, subUrl = '') { +async function getTestContext(overrides: Partial = {}, subUrl = '') { jest.clearAllMocks(); config.appSubUrl = subUrl; locationUtil.initialize({ config, getTimeRangeForUrl: jest.fn(), getVariablesUrlParams: jest.fn() }); @@ -38,33 +50,34 @@ function getTestContext(overrides: Partial = {}, subUrl = '') { ); + // Need to click this first to set the correct selection range + // see https://github.com/testing-library/user-event/issues/901#issuecomment-1087192424 + await userEvent.click(document.body); return { rerender, pushMock }; } describe('NavBarItem', () => { describe('when url property is not set', () => { - it('then it renders the menu trigger as a button', () => { - getTestContext(); + it('then it renders the menu trigger as a button', async () => { + await getTestContext(); expect(screen.getAllByRole('button')).toHaveLength(1); }); describe('and clicking on the menu trigger button', () => { - it('then the onClick handler should be called', () => { - getTestContext(); + it('then the onClick handler should be called', async () => { + await getTestContext(); - act(() => { - userEvent.click(screen.getByRole('button')); - }); + await userEvent.click(screen.getByRole('button')); expect(onClickMock).toHaveBeenCalledTimes(1); }); }); describe('and hovering over the menu trigger button', () => { - it('then the menu items should be visible', () => { - getTestContext(); + it('then the menu items should be visible', async () => { + await getTestContext(); - userEvent.hover(screen.getByRole('button')); + await userEvent.hover(screen.getByRole('button')); expect(screen.getByRole('menuitem', { name: 'Parent Node' })).toBeInTheDocument(); expect(screen.getByText('Child Node 1')).toBeInTheDocument(); @@ -73,10 +86,10 @@ describe('NavBarItem', () => { }); describe('and tabbing to the menu trigger button', () => { - it('then the menu items should be visible', () => { - getTestContext(); + it('then the menu items should be visible', async () => { + await getTestContext(); - userEvent.tab(); + await userEvent.tab(); expect(screen.getByText('Parent Node')).toBeInTheDocument(); expect(screen.getByText('Child Node 1')).toBeInTheDocument(); @@ -85,16 +98,16 @@ describe('NavBarItem', () => { }); describe('and pressing arrow right on the menu trigger button', () => { - it('then the correct menu item should receive focus', () => { - getTestContext(); + it('then the correct menu item should receive focus', async () => { + await getTestContext(); - userEvent.tab(); + await userEvent.tab(); expect(screen.getAllByRole('menuitem')).toHaveLength(3); expect(screen.getByRole('menuitem', { name: 'Parent Node' })).toHaveAttribute('tabIndex', '-1'); expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - userEvent.keyboard('{arrowright}'); + await userEvent.keyboard('{ArrowRight}'); expect(screen.getAllByRole('menuitem')).toHaveLength(3); expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '0'); expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); @@ -104,18 +117,18 @@ describe('NavBarItem', () => { }); describe('when url property is set', () => { - it('then it renders the menu trigger as a link', () => { - getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); + it('then it renders the menu trigger as a link', async () => { + await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); expect(screen.getAllByRole('link')).toHaveLength(1); expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.grafana.com'); }); describe('and hovering over the menu trigger link', () => { - it('then the menu items should be visible', () => { - getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); + it('then the menu items should be visible', async () => { + await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - userEvent.hover(screen.getByRole('link')); + await userEvent.hover(screen.getByRole('link')); expect(screen.getByText('Parent Node')).toBeInTheDocument(); expect(screen.getByText('Child Node 1')).toBeInTheDocument(); @@ -124,10 +137,10 @@ describe('NavBarItem', () => { }); describe('and tabbing to the menu trigger link', () => { - it('then the menu items should be visible', () => { - getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); + it('then the menu items should be visible', async () => { + await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - userEvent.tab(); + await userEvent.tab(); expect(screen.getByText('Parent Node')).toBeInTheDocument(); expect(screen.getByText('Child Node 1')).toBeInTheDocument(); @@ -136,17 +149,17 @@ describe('NavBarItem', () => { }); describe('and pressing arrow right on the menu trigger link', () => { - it('then the correct menu item should receive focus', () => { - getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); + it('then the correct menu item should receive focus', async () => { + await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - userEvent.tab(); + await userEvent.tab(); expect(screen.getAllByRole('link')[0]).toHaveFocus(); expect(screen.getAllByRole('menuitem')).toHaveLength(3); expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '-1'); expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - userEvent.keyboard('{arrowright}'); + await userEvent.keyboard('{ArrowRight}'); expect(screen.getAllByRole('link')[0]).not.toHaveFocus(); expect(screen.getAllByRole('menuitem')).toHaveLength(3); expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '0'); @@ -156,18 +169,18 @@ describe('NavBarItem', () => { }); describe('and pressing arrow left on a menu item', () => { - it('then the nav bar item should receive focus', () => { - getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); + it('then the nav bar item should receive focus', async () => { + await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com' } }); - userEvent.tab(); - userEvent.keyboard('{arrowright}'); + await userEvent.tab(); + await userEvent.keyboard('{ArrowRight}'); expect(screen.getAllByRole('link')[0]).not.toHaveFocus(); expect(screen.getAllByRole('menuitem')).toHaveLength(3); expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '0'); expect(screen.getAllByRole('menuitem')[1]).toHaveAttribute('tabIndex', '-1'); expect(screen.getAllByRole('menuitem')[2]).toHaveAttribute('tabIndex', '-1'); - userEvent.keyboard('{arrowleft}'); + await userEvent.keyboard('{ArrowLeft}'); expect(screen.getAllByRole('link')[0]).toHaveFocus(); expect(screen.getAllByRole('menuitem')).toHaveLength(3); expect(screen.getAllByRole('menuitem')[0]).toHaveAttribute('tabIndex', '-1'); @@ -178,7 +191,7 @@ describe('NavBarItem', () => { describe('when appSubUrl is configured and user clicks on menuitem link', () => { it('then location service should be called with correct url', async () => { - const { pushMock } = getTestContext( + const { pushMock } = await getTestContext( { link: { ...defaults.link, @@ -189,15 +202,13 @@ describe('NavBarItem', () => { '/grafana' ); - userEvent.hover(screen.getByRole('link')); + await userEvent.hover(screen.getByRole('link')); await waitFor(() => { expect(screen.getByText('Parent Node')).toBeInTheDocument(); expect(screen.getByText('New')).toBeInTheDocument(); }); - act(() => { - userEvent.click(screen.getByText('New')); - }); + await userEvent.click(screen.getByText('New')); await waitFor(() => { expect(pushMock).toHaveBeenCalledTimes(1); expect(pushMock).toHaveBeenCalledWith('/dashboard/new'); @@ -207,7 +218,7 @@ describe('NavBarItem', () => { describe('when appSubUrl is not configured and user clicks on menuitem link', () => { it('then location service should be called with correct url', async () => { - const { pushMock } = getTestContext({ + const { pushMock } = await getTestContext({ link: { ...defaults.link, url: 'https://www.grafana.com', @@ -215,15 +226,13 @@ describe('NavBarItem', () => { }, }); - userEvent.hover(screen.getByRole('link')); + await userEvent.hover(screen.getByRole('link')); await waitFor(() => { expect(screen.getByText('Parent Node')).toBeInTheDocument(); expect(screen.getByText('New')).toBeInTheDocument(); }); - act(() => { - userEvent.click(screen.getByText('New')); - }); + await userEvent.click(screen.getByText('New')); await waitFor(() => { expect(pushMock).toHaveBeenCalledTimes(1); expect(pushMock).toHaveBeenCalledWith('/grafana/dashboard/new'); diff --git a/public/app/core/components/NavBar/NavBarItem.tsx b/public/app/core/components/NavBar/NavBarItem.tsx index cf91ade17a4..55c2b339b0d 100644 --- a/public/app/core/components/NavBar/NavBarItem.tsx +++ b/public/app/core/components/NavBar/NavBarItem.tsx @@ -1,17 +1,18 @@ -import React, { ReactNode } from 'react'; -import { Item } from '@react-stately/collections'; import { css, cx } from '@emotion/css'; -import { GrafanaTheme2, locationUtil, NavMenuItemType, NavModelItem } from '@grafana/data'; -import { IconName, useTheme2 } from '@grafana/ui'; -import { locationService } from '@grafana/runtime'; - -import { NavBarMenuItem } from './NavBarMenuItem'; -import { getNavBarItemWithoutMenuStyles, NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; -import { NavBarItemMenuTrigger } from './NavBarItemMenuTrigger'; -import { NavBarItemMenu } from './NavBarItemMenu'; -import { getNavModelItemKey } from './utils'; import { useLingui } from '@lingui/react'; +import { Item } from '@react-stately/collections'; +import React, { ReactNode } from 'react'; + +import { GrafanaTheme2, locationUtil, NavMenuItemType, NavModelItem } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { IconName, useTheme2 } from '@grafana/ui'; + +import { NavBarItemMenu } from './NavBarItemMenu'; +import { NavBarItemMenuTrigger } from './NavBarItemMenuTrigger'; +import { getNavBarItemWithoutMenuStyles, NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; +import { NavBarMenuItem } from './NavBarMenuItem'; import menuItemTranslations from './navBarItem-translations'; +import { getNavModelItemKey } from './utils'; export interface Props { isActive?: boolean; diff --git a/public/app/core/components/NavBar/NavBarItemMenu.tsx b/public/app/core/components/NavBar/NavBarItemMenu.tsx index bff23a204a1..0fe05ead0ab 100644 --- a/public/app/core/components/NavBar/NavBarItemMenu.tsx +++ b/public/app/core/components/NavBar/NavBarItemMenu.tsx @@ -1,15 +1,16 @@ -import React, { ReactElement, useEffect, useRef } from 'react'; import { css } from '@emotion/css'; -import { useTheme2 } from '@grafana/ui'; -import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data'; -import { SpectrumMenuProps } from '@react-types/menu'; import { useMenu } from '@react-aria/menu'; -import { useTreeState } from '@react-stately/tree'; import { mergeProps } from '@react-aria/utils'; +import { useTreeState } from '@react-stately/tree'; +import { SpectrumMenuProps } from '@react-types/menu'; +import React, { ReactElement, useEffect, useRef } from 'react'; + +import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; -import { getNavModelItemKey } from './utils'; -import { useNavBarItemMenuContext } from './context'; import { NavBarItemMenuItem } from './NavBarItemMenuItem'; +import { useNavBarItemMenuContext } from './context'; +import { getNavModelItemKey } from './utils'; export interface NavBarItemMenuProps extends SpectrumMenuProps { onNavigate: (item: NavModelItem) => void; diff --git a/public/app/core/components/NavBar/NavBarItemMenuItem.tsx b/public/app/core/components/NavBar/NavBarItemMenuItem.tsx index 4508e7f171c..ce1878789af 100644 --- a/public/app/core/components/NavBar/NavBarItemMenuItem.tsx +++ b/public/app/core/components/NavBar/NavBarItemMenuItem.tsx @@ -1,12 +1,13 @@ -import React, { ReactElement, useRef, useState } from 'react'; import { css } from '@emotion/css'; -import { useTheme2 } from '@grafana/ui'; -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { useMenuItem } from '@react-aria/menu'; import { useFocus, useKeyboard } from '@react-aria/interactions'; -import { TreeState } from '@react-stately/tree'; +import { useMenuItem } from '@react-aria/menu'; import { mergeProps } from '@react-aria/utils'; +import { TreeState } from '@react-stately/tree'; import { Node } from '@react-types/shared'; +import React, { ReactElement, useRef, useState } from 'react'; + +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; import { useNavBarItemMenuContext } from './context'; diff --git a/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx b/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx index a38fac31ede..cabdb29c934 100644 --- a/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx +++ b/public/app/core/components/NavBar/NavBarItemMenuTrigger.tsx @@ -1,19 +1,20 @@ -import React, { ReactElement, useEffect, useState } from 'react'; import { css, cx } from '@emotion/css'; -import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { MenuTriggerProps } from '@react-types/menu'; -import { useMenuTriggerState } from '@react-stately/menu'; -import { useMenuTrigger } from '@react-aria/menu'; -import { useFocusWithin, useHover, useKeyboard } from '@react-aria/interactions'; import { useButton } from '@react-aria/button'; import { useDialog } from '@react-aria/dialog'; -import { DismissButton, useOverlay } from '@react-aria/overlays'; import { FocusScope } from '@react-aria/focus'; +import { useFocusWithin, useHover, useKeyboard } from '@react-aria/interactions'; +import { useMenuTrigger } from '@react-aria/menu'; +import { DismissButton, useOverlay } from '@react-aria/overlays'; +import { useMenuTriggerState } from '@react-stately/menu'; +import { MenuTriggerProps } from '@react-types/menu'; +import React, { ReactElement, useEffect, useState } from 'react'; -import { NavBarItemMenuContext } from './context'; -import { NavFeatureHighlight } from './NavFeatureHighlight'; +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { reportExperimentView } from '@grafana/runtime'; +import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; + +import { NavFeatureHighlight } from './NavFeatureHighlight'; +import { NavBarItemMenuContext } from './context'; export interface NavBarItemMenuTriggerProps extends MenuTriggerProps { children: ReactElement; diff --git a/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx b/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx index e76f828e93d..3bbe1e4603a 100644 --- a/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx +++ b/public/app/core/components/NavBar/NavBarItemWithoutMenu.tsx @@ -1,7 +1,9 @@ -import React, { ReactNode } from 'react'; import { css, cx } from '@emotion/css'; +import React, { ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Link, useTheme2 } from '@grafana/ui'; + import { NavFeatureHighlight } from './NavFeatureHighlight'; export interface NavBarItemWithoutMenuProps { diff --git a/public/app/core/components/NavBar/NavBarMenu.test.tsx b/public/app/core/components/NavBar/NavBarMenu.test.tsx index 10e4569e239..34059b553ff 100644 --- a/public/app/core/components/NavBar/NavBarMenu.test.tsx +++ b/public/app/core/components/NavBar/NavBarMenu.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { NavModelItem } from '@grafana/data'; +import React from 'react'; import { render } from 'test/redux-rtl'; + +import { NavModelItem } from '@grafana/data'; + import { NavBarMenu } from './NavBarMenu'; describe('NavBarMenu', () => { @@ -23,10 +25,10 @@ describe('NavBarMenu', () => { expect(closeButton).toBeInTheDocument(); }); - it('clicking the close button calls the onClose callback', () => { + it('clicking the close button calls the onClose callback', async () => { const closeButton = screen.getByRole('button', { name: 'Close navigation menu' }); expect(closeButton).toBeInTheDocument(); - userEvent.click(closeButton); + await userEvent.click(closeButton); expect(mockOnClose).toHaveBeenCalled(); }); }); diff --git a/public/app/core/components/NavBar/NavBarMenu.tsx b/public/app/core/components/NavBar/NavBarMenu.tsx index 91317e753e9..6cd7becbb61 100644 --- a/public/app/core/components/NavBar/NavBarMenu.tsx +++ b/public/app/core/components/NavBar/NavBarMenu.tsx @@ -1,10 +1,12 @@ +import { css } from '@emotion/css'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { useOverlay } from '@react-aria/overlays'; import React, { useRef } from 'react'; + import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { CustomScrollbar, Icon, IconButton, IconName, useTheme2 } from '@grafana/ui'; -import { FocusScope } from '@react-aria/focus'; -import { useDialog } from '@react-aria/dialog'; -import { useOverlay } from '@react-aria/overlays'; -import { css } from '@emotion/css'; + import { NavBarMenuItem } from './NavBarMenuItem'; export interface Props { diff --git a/public/app/core/components/NavBar/NavBarMenuItem.test.tsx b/public/app/core/components/NavBar/NavBarMenuItem.test.tsx index 64fa8e530dd..42e308bc047 100644 --- a/public/app/core/components/NavBar/NavBarMenuItem.test.tsx +++ b/public/app/core/components/NavBar/NavBarMenuItem.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; import { BrowserRouter } from 'react-router-dom'; + import { NavBarMenuItem } from './NavBarMenuItem'; describe('NavBarMenuItem', () => { diff --git a/public/app/core/components/NavBar/NavBarMenuItem.tsx b/public/app/core/components/NavBar/NavBarMenuItem.tsx index b978cffc30e..3c84f54a806 100644 --- a/public/app/core/components/NavBar/NavBarMenuItem.tsx +++ b/public/app/core/components/NavBar/NavBarMenuItem.tsx @@ -1,7 +1,8 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; -import { css } from '@emotion/css'; export interface Props { icon?: IconName; diff --git a/public/app/core/components/NavBar/NavBarSection.tsx b/public/app/core/components/NavBar/NavBarSection.tsx index 55413be0d12..f4409b1e2a8 100644 --- a/public/app/core/components/NavBar/NavBarSection.tsx +++ b/public/app/core/components/NavBar/NavBarSection.tsx @@ -1,5 +1,6 @@ -import React, { ReactNode } from 'react'; import { css, cx } from '@emotion/css'; +import React, { ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useTheme2 } from '@grafana/ui'; diff --git a/public/app/core/components/NavBar/NavFeatureHighlight.tsx b/public/app/core/components/NavBar/NavFeatureHighlight.tsx index c698e959516..c51e322ace1 100644 --- a/public/app/core/components/NavBar/NavFeatureHighlight.tsx +++ b/public/app/core/components/NavBar/NavFeatureHighlight.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; export interface Props { children: JSX.Element; diff --git a/public/app/core/components/NavBar/Next/NavBarItem.tsx b/public/app/core/components/NavBar/Next/NavBarItem.tsx index 844686b6e00..8abff2d09df 100644 --- a/public/app/core/components/NavBar/Next/NavBarItem.tsx +++ b/public/app/core/components/NavBar/Next/NavBarItem.tsx @@ -1,18 +1,20 @@ -import React, { ReactNode } from 'react'; -import { Item } from '@react-stately/collections'; import { css, cx } from '@emotion/css'; -import { GrafanaTheme2, locationUtil, NavMenuItemType, NavModelItem } from '@grafana/data'; -import { IconName, useTheme2 } from '@grafana/ui'; -import { locationService } from '@grafana/runtime'; - -import { NavBarMenuItem } from './NavBarMenuItem'; -import { getNavBarItemWithoutMenuStyles, NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; -import { NavBarItemMenuTrigger } from './NavBarItemMenuTrigger'; -import { NavBarItemMenu } from './NavBarItemMenu'; -import { getNavModelItemKey } from '../utils'; import { useLingui } from '@lingui/react'; -import menuItemTranslations from '../navBarItem-translations'; +import { Item } from '@react-stately/collections'; +import React, { ReactNode } from 'react'; + +import { GrafanaTheme2, locationUtil, NavMenuItemType, NavModelItem } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { IconName, useTheme2 } from '@grafana/ui'; + import { useNavBarContext } from '../context'; +import menuItemTranslations from '../navBarItem-translations'; +import { getNavModelItemKey } from '../utils'; + +import { NavBarItemMenu } from './NavBarItemMenu'; +import { NavBarItemMenuTrigger } from './NavBarItemMenuTrigger'; +import { getNavBarItemWithoutMenuStyles, NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; +import { NavBarMenuItem } from './NavBarMenuItem'; export interface Props { isActive?: boolean; diff --git a/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx b/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx index 9b9695a8f97..bcc3b3863f3 100644 --- a/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx +++ b/public/app/core/components/NavBar/Next/NavBarItemMenu.tsx @@ -1,15 +1,16 @@ -import React, { ReactElement, useEffect, useRef } from 'react'; import { css } from '@emotion/css'; -import { useTheme2 } from '@grafana/ui'; -import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data'; -import { SpectrumMenuProps } from '@react-types/menu'; import { useMenu } from '@react-aria/menu'; -import { useTreeState } from '@react-stately/tree'; import { mergeProps } from '@react-aria/utils'; +import { useTreeState } from '@react-stately/tree'; +import { SpectrumMenuProps } from '@react-types/menu'; +import React, { ReactElement, useEffect, useRef } from 'react'; + +import { GrafanaTheme2, NavMenuItemType, NavModelItem } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; -import { getNavModelItemKey } from '../utils'; -import { useNavBarItemMenuContext } from '../context'; import { NavBarItemMenuItem } from '../NavBarItemMenuItem'; +import { useNavBarItemMenuContext } from '../context'; +import { getNavModelItemKey } from '../utils'; export interface NavBarItemMenuProps extends SpectrumMenuProps { onNavigate: (item: NavModelItem) => void; @@ -78,14 +79,11 @@ function getStyles(theme: GrafanaTheme2, reverseDirection?: boolean) { menu: css` background-color: ${theme.colors.background.primary}; border: 1px solid ${theme.components.panel.borderColor}; - bottom: ${reverseDirection ? 0 : 'auto'}; box-shadow: ${theme.shadows.z3}; display: flex; flex-direction: column; - left: 100%; list-style: none; min-width: 140px; - top: ${reverseDirection ? 'auto' : 0}; transition: ${theme.transitions.create('opacity')}; z-index: ${theme.zIndex.sidemenu}; `, diff --git a/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx b/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx index 75a216aa3e8..b2d148439b7 100644 --- a/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx +++ b/public/app/core/components/NavBar/Next/NavBarItemMenuTrigger.tsx @@ -1,19 +1,22 @@ -import React, { ReactElement, useEffect, useState } from 'react'; import { css, cx } from '@emotion/css'; -import { getPortalContainer, Icon, IconName, Link, useTheme2 } from '@grafana/ui'; -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { MenuTriggerProps } from '@react-types/menu'; -import { useMenuTriggerState } from '@react-stately/menu'; -import { useMenuTrigger } from '@react-aria/menu'; -import { useFocusWithin, useHover, useKeyboard } from '@react-aria/interactions'; import { useButton } from '@react-aria/button'; import { useDialog } from '@react-aria/dialog'; -import { DismissButton, OverlayContainer, useOverlay, useOverlayPosition } from '@react-aria/overlays'; import { FocusScope } from '@react-aria/focus'; +import { useFocusWithin, useHover, useKeyboard } from '@react-aria/interactions'; +import { useMenuTrigger } from '@react-aria/menu'; +import { DismissButton, OverlayContainer, useOverlay, useOverlayPosition } from '@react-aria/overlays'; +import { useMenuTriggerState } from '@react-stately/menu'; +import { MenuTriggerProps } from '@react-types/menu'; +import React, { ReactElement, useEffect, useState } from 'react'; -import { NavBarItemMenuContext, useNavBarContext } from '../context'; -import { NavFeatureHighlight } from '../NavFeatureHighlight'; +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; import { reportExperimentView } from '@grafana/runtime'; +import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; + +import { NavFeatureHighlight } from '../NavFeatureHighlight'; +import { NavBarItemMenuContext, useNavBarContext } from '../context'; + +import { getNavMenuPortalContainer } from './NavBarMenuPortalContainer'; export interface NavBarItemMenuTriggerProps extends MenuTriggerProps { children: ReactElement; @@ -101,6 +104,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE className={styles.element} {...buttonProps} {...keyboardProps} + {...hoverProps} ref={ref as React.RefObject} onClick={item?.onClick} aria-label={label} @@ -115,6 +119,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE } href={item.url} target={item.target} @@ -131,6 +136,7 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE onClick={item?.onClick} {...buttonProps} {...keyboardProps} + {...hoverProps} ref={ref as React.RefObject} className={styles.element} aria-label={label} @@ -179,10 +185,10 @@ export function NavBarItemMenuTrigger(props: NavBarItemMenuTriggerProps): ReactE }); return ( -
+
{element} {state.isOpen && ( - +
-
- {link.icon && } - {link.img && ( - {`${link.text} - )} -
+
{getLinkIcon(link)}
{link.text}
@@ -362,10 +361,7 @@ function CollapsibleNavItem({ className={styles.collapsibleMenuItem} elClassName={styles.collapsibleIcon} > - {link.img && ( - {`${link.text} - )} - {link.icon && } + {getLinkIcon(link)}
({ position: 'relative', display: 'grid', gridAutoFlow: 'column', - gridTemplateColumns: '56px auto', + gridTemplateColumns: `${theme.spacing(7)} auto`, }), collapsibleMenuItem: css({ height: theme.spacing(6), @@ -444,3 +440,15 @@ const getCollapsibleStyles = (theme: GrafanaTheme2) => ({ function linkHasChildren(link: NavModelItem): link is NavModelItem & { children: NavModelItem[] } { return Boolean(link.children && link.children.length > 0); } + +function getLinkIcon(link: NavModelItem) { + if (link.id === 'home') { + return ; + } else if (link.icon) { + return ; + } else if (link.img) { + return {`${link.text}; + } else { + return null; + } +} diff --git a/public/app/core/components/NavBar/Next/NavBarMenuItem.tsx b/public/app/core/components/NavBar/Next/NavBarMenuItem.tsx index 60caa5c074f..08f54daeb99 100644 --- a/public/app/core/components/NavBar/Next/NavBarMenuItem.tsx +++ b/public/app/core/components/NavBar/Next/NavBarMenuItem.tsx @@ -1,7 +1,8 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Icon, IconName, Link, useTheme2 } from '@grafana/ui'; -import { css, cx } from '@emotion/css'; export interface Props { icon?: IconName; @@ -99,6 +100,7 @@ const getStyles = (theme: GrafanaTheme2, isActive: Props['isActive'], hasIcon: b overflowWrap: 'anywhere', padding: !hasIcon ? `${theme.spacing(0.5, 2)}` : '5px 12px 5px 10px', textAlign: 'left', + width: '100%', '&:hover, &:focus-visible': { backgroundColor: theme.colors.action.hover, color: theme.colors.text.primary, diff --git a/public/app/core/components/NavBar/Next/NavBarMenuPortalContainer.tsx b/public/app/core/components/NavBar/Next/NavBarMenuPortalContainer.tsx new file mode 100644 index 00000000000..5d51ff0af89 --- /dev/null +++ b/public/app/core/components/NavBar/Next/NavBarMenuPortalContainer.tsx @@ -0,0 +1,27 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; + +const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container'; + +export const getNavMenuPortalContainer = () => document.getElementById(NAV_MENU_PORTAL_CONTAINER_ID) ?? document.body; + +export const NavBarMenuPortalContainer = () => { + const theme = useTheme2(); + const styles = getStyles(theme); + return
; +}; + +NavBarMenuPortalContainer.displayName = 'NavBarMenuPortalContainer'; + +const getStyles = (theme: GrafanaTheme2) => ({ + menuPortalContainer: css({ + left: 0, + position: 'fixed', + right: 0, + top: 0, + zIndex: theme.zIndex.sidemenu, + }), +}); diff --git a/public/app/core/components/NavBar/Next/NavBarNext.test.tsx b/public/app/core/components/NavBar/Next/NavBarNext.test.tsx index c7d21ddfa81..9c3c96f4de1 100644 --- a/public/app/core/components/NavBar/Next/NavBarNext.test.tsx +++ b/public/app/core/components/NavBar/Next/NavBarNext.test.tsx @@ -1,10 +1,13 @@ +import { render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; + import { locationService } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; + import TestProvider from '../../../../../test/helpers/TestProvider'; + import { NavBarNext } from './NavBarNext'; jest.mock('app/core/services/context_srv', () => ({ @@ -33,6 +36,17 @@ const setup = () => { }; describe('Render', () => { + beforeEach(() => { + // IntersectionObserver isn't available in test environment + const mockIntersectionObserver = jest.fn(); + mockIntersectionObserver.mockReturnValue({ + observe: () => null, + unobserve: () => null, + disconnect: () => null, + }); + window.IntersectionObserver = mockIntersectionObserver; + }); + it('should render component', async () => { setup(); const sidemenu = await screen.findByTestId('sidemenu'); diff --git a/public/app/core/components/NavBar/Next/NavBarNext.tsx b/public/app/core/components/NavBar/Next/NavBarNext.tsx index a2a07fbac5e..1b97427d1a4 100644 --- a/public/app/core/components/NavBar/Next/NavBarNext.tsx +++ b/public/app/core/components/NavBar/Next/NavBarNext.tsx @@ -1,20 +1,26 @@ -import React, { useState } from 'react'; -import { useLocation } from 'react-router-dom'; import { css, cx } from '@emotion/css'; +import { FocusScope } from '@react-aria/focus'; import { cloneDeep } from 'lodash'; +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; + import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data'; -import { Icon, IconName, useTheme2 } from '@grafana/ui'; import { config, locationService } from '@grafana/runtime'; +import { Icon, IconName, useTheme2 } from '@grafana/ui'; +import { Branding } from 'app/core/components/Branding/Branding'; import { getKioskMode } from 'app/core/navigation/kiosk'; import { KioskMode, StoreState } from 'app/types'; -import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive, SEARCH_ITEM_ID } from '../utils'; + import { OrgSwitcher } from '../../OrgSwitcher'; -import { NavBarMenu } from './NavBarMenu'; -import NavBarItem from './NavBarItem'; -import { useSelector } from 'react-redux'; -import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; -import { FocusScope } from '@react-aria/focus'; import { NavBarContext } from '../context'; +import { enrichConfigItems, getActiveItem, isMatchOrChildMatch, isSearchActive, SEARCH_ITEM_ID } from '../utils'; + +import NavBarItem from './NavBarItem'; +import { NavBarItemWithoutMenu } from './NavBarItemWithoutMenu'; +import { NavBarMenu } from './NavBarMenu'; +import { NavBarMenuPortalContainer } from './NavBarMenuPortalContainer'; +import { NavBarScrollContainer } from './NavBarScrollContainer'; import { NavBarToggle } from './NavBarToggle'; const onOpenSearch = () => { @@ -85,38 +91,48 @@ export const NavBarNext = React.memo(() => { onClick={() => setMenuOpen(!menuOpen)} /> + +
    - + - - - - {coreItems.map((link, index) => ( - - {link.icon && } - {link.img && {`${link.text}} + + + - ))} - {pluginItems.length > 0 && - pluginItems.map((link, index) => ( - + {coreItems.map((link, index) => ( + {link.icon && } {link.img && {`${link.text}} ))} + {pluginItems.length > 0 && + pluginItems.map((link, index) => ( + + {link.icon && } + {link.img && {`${link.text}} + + ))} + + {configItems.map((link, index) => ( ({ zIndex: theme.zIndex.sidemenu, padding: `${theme.spacing(1)} 0`, position: 'relative', - width: theme.spacing(7), + width: `calc(${theme.spacing(7)} + 1px)`, borderRight: `1px solid ${theme.colors.border.weak}`, [theme.breakpoints.down('md')]: { @@ -204,13 +220,22 @@ const getStyles = (theme: GrafanaTheme2) => ({ }, }), grafanaLogo: css({ + alignItems: 'stretch', + display: 'flex', + flexShrink: 0, + justifyContent: 'stretch', + }), + grafanaLogoInner: css({ alignItems: 'center', display: 'flex', - img: { - height: theme.spacing(3), - width: theme.spacing(3), - }, + height: '100%', justifyContent: 'center', + width: '100%', + + '> div': { + height: 'auto', + width: 'auto', + }, }), search: css({ display: 'none', @@ -241,4 +266,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ right: '0px', transform: `translateX(50%)`, }), + menuPortalContainer: css({ + zIndex: theme.zIndex.sidemenu, + }), }); diff --git a/public/app/core/components/NavBar/Next/NavBarScrollContainer.tsx b/public/app/core/components/NavBar/Next/NavBarScrollContainer.tsx new file mode 100644 index 00000000000..91cc70fb680 --- /dev/null +++ b/public/app/core/components/NavBar/Next/NavBarScrollContainer.tsx @@ -0,0 +1,130 @@ +import { css, cx } from '@emotion/css'; +import React, { useEffect, useRef, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { CustomScrollbar, Icon, useTheme2 } from '@grafana/ui'; + +export interface Props { + children: React.ReactNode; +} + +export const NavBarScrollContainer = ({ children }: Props) => { + const [showScrollTopIndicator, setShowTopScrollIndicator] = useState(false); + const [showScrollBottomIndicator, setShowBottomScrollIndicator] = useState(false); + const scrollTopMarker = useRef(null); + const scrollBottomMarker = useRef(null); + const theme = useTheme2(); + const styles = getStyles(theme); + + // Here we observe the top and bottom markers to determine if we should show the scroll indicators + useEffect(() => { + const intersectionObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.target === scrollTopMarker.current) { + setShowTopScrollIndicator(!entry.isIntersecting); + } else if (entry.target === scrollBottomMarker.current) { + setShowBottomScrollIndicator(!entry.isIntersecting); + } + }); + }); + [scrollTopMarker, scrollBottomMarker].forEach((ref) => { + if (ref.current) { + intersectionObserver.observe(ref.current); + } + }); + return () => intersectionObserver.disconnect(); + }, []); + + return ( + +
    + +
    +
    +
    + {children} +
    +
    +
    + +
    +
    + ); +}; + +NavBarScrollContainer.displayName = 'NavBarScrollContainer'; + +const getStyles = (theme: GrafanaTheme2) => ({ + 'scrollTopMarker, scrollBottomMarker': css({ + height: theme.spacing(1), + left: 0, + position: 'absolute', + pointerEvents: 'none', + right: 0, + }), + scrollTopMarker: css({ + top: 0, + }), + scrollBottomMarker: css({ + bottom: 0, + }), + scrollContent: css({ + position: 'relative', + }), + // override the scroll container position so that the scroll indicators + // are positioned at the top and bottom correctly. + // react-custom-scrollbars doesn't provide any way for us to hook in nicely, + // so we have to override with !important. feelsbad. + scrollContainer: css` + .scrollbar-view { + position: static !important; + } + `, + scrollTopIndicator: css({ + background: `linear-gradient(0deg, transparent, ${theme.colors.background.canvas})`, + height: theme.spacing(6), + left: 0, + opacity: 0, + pointerEvents: 'none', + position: 'absolute', + right: 0, + top: 0, + transition: theme.transitions.create('opacity'), + zIndex: theme.zIndex.sidemenu, + }), + scrollBottomIndicator: css({ + background: `linear-gradient(0deg, ${theme.colors.background.canvas}, transparent)`, + bottom: 0, + height: theme.spacing(6), + left: 0, + opacity: 0, + pointerEvents: 'none', + position: 'absolute', + right: 0, + transition: theme.transitions.create('opacity'), + zIndex: theme.zIndex.sidemenu, + }), + scrollIndicatorVisible: css({ + opacity: 1, + }), + scrollTopIcon: css({ + left: '50%', + position: 'absolute', + top: 0, + transform: 'translateX(-50%)', + }), + scrollBottomIcon: css({ + bottom: 0, + left: '50%', + position: 'absolute', + transform: 'translateX(-50%)', + }), +}); diff --git a/public/app/core/components/NavBar/Next/NavBarToggle.tsx b/public/app/core/components/NavBar/Next/NavBarToggle.tsx index 617958cc046..c913464e4b4 100644 --- a/public/app/core/components/NavBar/Next/NavBarToggle.tsx +++ b/public/app/core/components/NavBar/Next/NavBarToggle.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { IconButton, useTheme2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; import classnames from 'classnames'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { IconButton, useTheme2 } from '@grafana/ui'; export interface Props { className?: string; @@ -33,7 +34,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ border: `1px solid ${theme.colors.border.weak}`, borderRadius: '50%', marginRight: 0, - zIndex: theme.zIndex.sidemenu, + zIndex: theme.zIndex.sidemenu + 1, [theme.breakpoints.down('md')]: { display: 'none', diff --git a/public/app/core/components/NavBar/navBarItem-translations.ts b/public/app/core/components/NavBar/navBarItem-translations.ts index d602a7584b8..51fbb29f32d 100644 --- a/public/app/core/components/NavBar/navBarItem-translations.ts +++ b/public/app/core/components/NavBar/navBarItem-translations.ts @@ -1,5 +1,5 @@ -import { defineMessage } from '@lingui/macro'; import { MessageDescriptor } from '@lingui/core'; +import { defineMessage } from '@lingui/macro'; // Maps the ID of the nav item to a translated phrase to later pass to // Because the navigation content is dynamic (defined in the backend), we can not use diff --git a/public/app/core/components/NavBar/utils.test.ts b/public/app/core/components/NavBar/utils.test.ts index 8da0b6bc565..23ac4adb156 100644 --- a/public/app/core/components/NavBar/utils.test.ts +++ b/public/app/core/components/NavBar/utils.test.ts @@ -1,7 +1,10 @@ import { Location } from 'history'; + import { NavModelItem } from '@grafana/data'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; + import { getConfig, updateConfig } from '../../config'; + import { enrichConfigItems, getActiveItem, getForcedLoginUrl, isMatchOrChildMatch, isSearchActive } from './utils'; jest.mock('../../app_events', () => ({ diff --git a/public/app/core/components/NavBar/utils.ts b/public/app/core/components/NavBar/utils.ts index 80b8bca775f..83bc8705c44 100644 --- a/public/app/core/components/NavBar/utils.ts +++ b/public/app/core/components/NavBar/utils.ts @@ -1,13 +1,18 @@ import { Location } from 'history'; + import { NavModelItem, NavSection } from '@grafana/data'; import { getConfig } from 'app/core/config'; import { contextSrv } from 'app/core/services/context_srv'; + import { ShowModalReactEvent } from '../../../types/events'; import appEvents from '../../app_events'; import { getFooterLinks } from '../Footer/Footer'; import { HelpModal } from '../help/HelpModal'; export const SEARCH_ITEM_ID = 'search'; +export const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container'; + +export const getNavMenuPortalContainer = () => document.getElementById(NAV_MENU_PORTAL_CONTAINER_ID) ?? document.body; export const getForcedLoginUrl = (url: string) => { const queryParams = new URLSearchParams(url.split('?')[1]); diff --git a/public/app/core/components/NodeGraphSettings.tsx b/public/app/core/components/NodeGraphSettings.tsx index 669376a947e..6f305d4d7c3 100644 --- a/public/app/core/components/NodeGraphSettings.tsx +++ b/public/app/core/components/NodeGraphSettings.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { DataSourceJsonData, DataSourcePluginOptionsEditorProps, diff --git a/public/app/core/components/OrgSwitcher.tsx b/public/app/core/components/OrgSwitcher.tsx index 79189a12b56..1b89d7b0a14 100644 --- a/public/app/core/components/OrgSwitcher.tsx +++ b/public/app/core/components/OrgSwitcher.tsx @@ -1,12 +1,13 @@ -import React, { ReactElement } from 'react'; import { css } from '@emotion/css'; +import React, { ReactElement } from 'react'; +import { useAsync } from 'react-use'; + import { UserOrgDTO } from '@grafana/data'; import { Button, CustomScrollbar, Modal } from '@grafana/ui'; - -import { contextSrv } from 'app/core/services/context_srv'; import config from 'app/core/config'; +import { contextSrv } from 'app/core/services/context_srv'; + import { api } from '../../features/profile/api'; -import { useAsync } from 'react-use'; interface Props { onDismiss: () => void; diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 0fdecd751b7..121b9ba1548 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -1,15 +1,17 @@ // Libraries +import { css, cx } from '@emotion/css'; import React, { FC, HTMLAttributes, useEffect } from 'react'; + +import { GrafanaTheme2, NavModel } from '@grafana/data'; +import { CustomScrollbar, useStyles2 } from '@grafana/ui'; import { getTitleFromNavModel } from 'app/core/selectors/navModel'; // Components -import PageHeader from '../PageHeader/PageHeader'; -import { Footer } from '../Footer/Footer'; -import { PageContents } from './PageContents'; -import { CustomScrollbar, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, NavModel } from '@grafana/data'; import { Branding } from '../Branding/Branding'; -import { css, cx } from '@emotion/css'; +import { Footer } from '../Footer/Footer'; +import PageHeader from '../PageHeader/PageHeader'; + +import { PageContents } from './PageContents'; interface Props extends HTMLAttributes { children: React.ReactNode; diff --git a/public/app/core/components/Page/PageContents.tsx b/public/app/core/components/Page/PageContents.tsx index edfc9a019a6..cfbd1b850c6 100644 --- a/public/app/core/components/Page/PageContents.tsx +++ b/public/app/core/components/Page/PageContents.tsx @@ -1,6 +1,6 @@ // Libraries -import React, { FC } from 'react'; import { cx } from '@emotion/css'; +import React, { FC } from 'react'; // Components import PageLoader from '../PageLoader/PageLoader'; diff --git a/public/app/core/components/PageActionBar/PageActionBar.test.tsx b/public/app/core/components/PageActionBar/PageActionBar.test.tsx index 93f35b7100b..9e68addc3a2 100644 --- a/public/app/core/components/PageActionBar/PageActionBar.test.tsx +++ b/public/app/core/components/PageActionBar/PageActionBar.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import PageActionBar, { Props } from './PageActionBar'; const setup = (propOverrides?: object) => { diff --git a/public/app/core/components/PageActionBar/PageActionBar.tsx b/public/app/core/components/PageActionBar/PageActionBar.tsx index a3471875063..eac3fcc8f3c 100644 --- a/public/app/core/components/PageActionBar/PageActionBar.tsx +++ b/public/app/core/components/PageActionBar/PageActionBar.tsx @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; + import { LinkButton, FilterInput } from '@grafana/ui'; export interface Props { diff --git a/public/app/core/components/PageHeader/PageHeader.test.tsx b/public/app/core/components/PageHeader/PageHeader.test.tsx index 03ac4b58663..2965d007142 100644 --- a/public/app/core/components/PageHeader/PageHeader.test.tsx +++ b/public/app/core/components/PageHeader/PageHeader.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import PageHeader from './PageHeader'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import PageHeader from './PageHeader'; describe('PageHeader', () => { describe('when the nav tree has a node with a title', () => { diff --git a/public/app/core/components/PageHeader/PageHeader.tsx b/public/app/core/components/PageHeader/PageHeader.tsx index 8d770f6f051..116ff8186dd 100644 --- a/public/app/core/components/PageHeader/PageHeader.tsx +++ b/public/app/core/components/PageHeader/PageHeader.tsx @@ -1,8 +1,10 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; -import { Tab, TabsBar, Icon, IconName, useStyles2 } from '@grafana/ui'; +import React, { FC } from 'react'; + import { NavModel, NavModelItem, NavModelBreadcrumb, GrafanaTheme2 } from '@grafana/data'; +import { Tab, TabsBar, Icon, IconName, useStyles2 } from '@grafana/ui'; import { PanelHeaderMenuItem } from 'app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem'; + import { ProBadge } from '../Upgrade/ProBadge'; export interface Props { diff --git a/public/app/core/components/PageLoader/PageLoader.tsx b/public/app/core/components/PageLoader/PageLoader.tsx index ff4f4dc4c20..d7aee52c271 100644 --- a/public/app/core/components/PageLoader/PageLoader.tsx +++ b/public/app/core/components/PageLoader/PageLoader.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { LoadingPlaceholder } from '@grafana/ui'; interface Props { diff --git a/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx b/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx index a0cd6362a56..5e8a27c8b1b 100644 --- a/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx +++ b/public/app/core/components/PanelTypeFilter/PanelTypeFilter.tsx @@ -1,8 +1,9 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; -import { getAllPanelPluginMeta } from 'app/features/panel/state/util'; -import { Icon, Button, MultiSelect, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; +import { Icon, Button, MultiSelect, useStyles2 } from '@grafana/ui'; +import { getAllPanelPluginMeta } from 'app/features/panel/state/util'; export interface Props { onChange: (plugins: PanelPluginMeta[]) => void; diff --git a/public/app/core/components/PasswordField/PasswordField.test.tsx b/public/app/core/components/PasswordField/PasswordField.test.tsx index 98af3c901ba..3b985730768 100644 --- a/public/app/core/components/PasswordField/PasswordField.test.tsx +++ b/public/app/core/components/PasswordField/PasswordField.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { PasswordField } from './PasswordField'; describe('PasswordField', () => { diff --git a/public/app/core/components/PasswordField/PasswordField.tsx b/public/app/core/components/PasswordField/PasswordField.tsx index ec32963c364..1571f17186d 100644 --- a/public/app/core/components/PasswordField/PasswordField.tsx +++ b/public/app/core/components/PasswordField/PasswordField.tsx @@ -1,6 +1,6 @@ import React, { FC, useState } from 'react'; -import { selectors } from '@grafana/e2e-selectors'; +import { selectors } from '@grafana/e2e-selectors'; import { Input, IconButton } from '@grafana/ui'; export interface Props { diff --git a/public/app/core/components/PermissionList/AddPermission.tsx b/public/app/core/components/PermissionList/AddPermission.tsx index 3555c6ef4db..1095e782622 100644 --- a/public/app/core/components/PermissionList/AddPermission.tsx +++ b/public/app/core/components/PermissionList/AddPermission.tsx @@ -1,10 +1,11 @@ -import React, { Component } from 'react'; import { css } from '@emotion/css'; -import config from 'app/core/config'; -import { UserPicker } from 'app/core/components/Select/UserPicker'; -import { TeamPicker } from 'app/core/components/Select/TeamPicker'; -import { Button, Form, HorizontalGroup, Select, stylesFactory } from '@grafana/ui'; +import React, { Component } from 'react'; + import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { Button, Form, HorizontalGroup, Select, stylesFactory } from '@grafana/ui'; +import { TeamPicker } from 'app/core/components/Select/TeamPicker'; +import { UserPicker } from 'app/core/components/Select/UserPicker'; +import config from 'app/core/config'; import { OrgUser, Team } from 'app/types'; import { dashboardPermissionLevels, @@ -14,6 +15,7 @@ import { NewDashboardAclItem, OrgRole, } from 'app/types/acl'; + import { CloseButton } from '../CloseButton/CloseButton'; export interface Props { diff --git a/public/app/core/components/PermissionList/DisabledPermissionListItem.tsx b/public/app/core/components/PermissionList/DisabledPermissionListItem.tsx index a278cc29db0..2a96b8a25fd 100644 --- a/public/app/core/components/PermissionList/DisabledPermissionListItem.tsx +++ b/public/app/core/components/PermissionList/DisabledPermissionListItem.tsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; + import { Select, Icon, Button } from '@grafana/ui'; import { dashboardPermissionLevels } from 'app/types/acl'; diff --git a/public/app/core/components/PermissionList/PermissionList.tsx b/public/app/core/components/PermissionList/PermissionList.tsx index 043edc4bd27..02f4aee63fa 100644 --- a/public/app/core/components/PermissionList/PermissionList.tsx +++ b/public/app/core/components/PermissionList/PermissionList.tsx @@ -1,9 +1,11 @@ import React, { PureComponent } from 'react'; -import PermissionsListItem from './PermissionListItem'; -import DisabledPermissionsListItem from './DisabledPermissionListItem'; + import { FolderInfo } from 'app/types'; import { DashboardAcl } from 'app/types/acl'; +import DisabledPermissionsListItem from './DisabledPermissionListItem'; +import PermissionsListItem from './PermissionListItem'; + export interface Props { items: DashboardAcl[]; onRemoveItem: (item: DashboardAcl) => void; diff --git a/public/app/core/components/PermissionList/PermissionListItem.tsx b/public/app/core/components/PermissionList/PermissionListItem.tsx index e7ecf725494..35462db0b04 100644 --- a/public/app/core/components/PermissionList/PermissionListItem.tsx +++ b/public/app/core/components/PermissionList/PermissionListItem.tsx @@ -1,8 +1,9 @@ import React, { PureComponent } from 'react'; -import { Select, Icon, Button } from '@grafana/ui'; + import { SelectableValue } from '@grafana/data'; -import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl'; +import { Select, Icon, Button } from '@grafana/ui'; import { FolderInfo } from 'app/types'; +import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl'; const setClassNameHelper = (inherited: boolean) => { return inherited ? 'gf-form-disabled' : ''; diff --git a/public/app/core/components/PluginHelp/PluginHelp.tsx b/public/app/core/components/PluginHelp/PluginHelp.tsx index cf55ac09ee3..7ead1046e22 100644 --- a/public/app/core/components/PluginHelp/PluginHelp.tsx +++ b/public/app/core/components/PluginHelp/PluginHelp.tsx @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; + import { renderMarkdown } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; diff --git a/public/app/core/components/QueryOperationRow/OperationRowHelp.tsx b/public/app/core/components/QueryOperationRow/OperationRowHelp.tsx index 874e900038e..9283db39cc7 100644 --- a/public/app/core/components/QueryOperationRow/OperationRowHelp.tsx +++ b/public/app/core/components/QueryOperationRow/OperationRowHelp.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2, renderMarkdown } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; diff --git a/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx b/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx index f61b06c0de6..1b6c4b328f6 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { QueryOperationAction } from './QueryOperationAction'; import { shallow } from 'enzyme'; +import React from 'react'; + +import { QueryOperationAction } from './QueryOperationAction'; describe('QueryOperationAction', () => { it('renders', () => { diff --git a/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx b/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx index 3f7f25806be..4c59c1ad952 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationAction.tsx @@ -1,8 +1,9 @@ -import { IconButton, IconName, useStyles2 } from '@grafana/ui'; -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { IconButton, IconName, useStyles2 } from '@grafana/ui'; interface QueryOperationActionProps { icon: IconName; diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx index 400cc554ae5..afcccbf93fb 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { QueryOperationRow } from './QueryOperationRow'; import { mount, shallow } from 'enzyme'; +import React from 'react'; import { act } from 'react-dom/test-utils'; +import { QueryOperationRow } from './QueryOperationRow'; + describe('QueryOperationRow', () => { it('renders', () => { expect(() => diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx index 4004424e07f..85f32fdfa08 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx @@ -1,10 +1,11 @@ -import React, { useCallback, useState } from 'react'; -import { Icon, ReactUtils, stylesFactory, useTheme } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { useUpdateEffect } from 'react-use'; +import React, { useCallback, useState } from 'react'; import { Draggable } from 'react-beautiful-dnd'; +import { useUpdateEffect } from 'react-use'; + +import { GrafanaTheme } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; +import { Icon, ReactUtils, stylesFactory, useTheme } from '@grafana/ui'; interface QueryOperationRowProps { index: number; diff --git a/public/app/core/components/RolePicker/RolePicker.tsx b/public/app/core/components/RolePicker/RolePicker.tsx index 7e31e4fb1d3..a916ed9a1d2 100644 --- a/public/app/core/components/RolePicker/RolePicker.tsx +++ b/public/app/core/components/RolePicker/RolePicker.tsx @@ -1,9 +1,11 @@ import React, { FormEvent, useCallback, useEffect, useState } from 'react'; + import { ClickOutsideWrapper, HorizontalGroup, Spinner } from '@grafana/ui'; -import { RolePickerMenu } from './RolePickerMenu'; -import { RolePickerInput } from './RolePickerInput'; import { Role, OrgRole } from 'app/types'; +import { RolePickerInput } from './RolePickerInput'; +import { RolePickerMenu } from './RolePickerMenu'; + export interface Props { builtInRole?: OrgRole; appliedRoles: Role[]; diff --git a/public/app/core/components/RolePicker/RolePickerInput.tsx b/public/app/core/components/RolePicker/RolePickerInput.tsx index 3108e2c0b2e..b650439532d 100644 --- a/public/app/core/components/RolePicker/RolePickerInput.tsx +++ b/public/app/core/components/RolePicker/RolePickerInput.tsx @@ -1,10 +1,13 @@ -import React, { FormEvent, HTMLProps, useEffect, useRef } from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2, getInputStyles, sharedInputStyle, styleMixins, Tooltip, Icon } from '@grafana/ui'; +import React, { FormEvent, HTMLProps, useEffect, useRef } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { ValueContainer } from './ValueContainer'; +import { useStyles2, getInputStyles, sharedInputStyle, styleMixins, Tooltip, Icon } from '@grafana/ui'; + import { Role } from '../../../types'; +import { ValueContainer } from './ValueContainer'; + const stopPropagation = (event: React.MouseEvent) => event.stopPropagation(); interface InputProps extends HTMLProps { diff --git a/public/app/core/components/RolePicker/RolePickerMenu.tsx b/public/app/core/components/RolePicker/RolePickerMenu.tsx index 74c66a4aa0e..6ecc86fe487 100644 --- a/public/app/core/components/RolePicker/RolePickerMenu.tsx +++ b/public/app/core/components/RolePicker/RolePickerMenu.tsx @@ -1,5 +1,7 @@ -import React, { FormEvent, useEffect, useRef, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FormEvent, useEffect, useRef, useState } from 'react'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Button, Checkbox, @@ -12,7 +14,6 @@ import { useStyles2, useTheme2, } from '@grafana/ui'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles'; import { OrgRole, Role } from 'app/types'; diff --git a/public/app/core/components/RolePicker/TeamRolePicker.tsx b/public/app/core/components/RolePicker/TeamRolePicker.tsx index 3c35a342b82..dafbd3e78ab 100644 --- a/public/app/core/components/RolePicker/TeamRolePicker.tsx +++ b/public/app/core/components/RolePicker/TeamRolePicker.tsx @@ -1,6 +1,8 @@ import React, { FC, useEffect } from 'react'; import { useAsyncFn } from 'react-use'; + import { Role } from 'app/types'; + import { RolePicker } from './RolePicker'; import { fetchTeamRoles, updateTeamRoles } from './api'; diff --git a/public/app/core/components/RolePicker/UserRolePicker.tsx b/public/app/core/components/RolePicker/UserRolePicker.tsx index 5e5e1d2ffeb..e32bcb746ae 100644 --- a/public/app/core/components/RolePicker/UserRolePicker.tsx +++ b/public/app/core/components/RolePicker/UserRolePicker.tsx @@ -1,7 +1,9 @@ import React, { FC, useEffect } from 'react'; import { useAsyncFn } from 'react-use'; + import { contextSrv } from 'app/core/core'; import { Role, OrgRole, AccessControlAction } from 'app/types'; + import { RolePicker } from './RolePicker'; import { fetchUserRoles, updateUserRoles } from './api'; diff --git a/public/app/core/components/RolePicker/ValueContainer.tsx b/public/app/core/components/RolePicker/ValueContainer.tsx index 844819364e0..32c409892ac 100644 --- a/public/app/core/components/RolePicker/ValueContainer.tsx +++ b/public/app/core/components/RolePicker/ValueContainer.tsx @@ -1,7 +1,8 @@ -import React, { ReactNode } from 'react'; import { css, cx } from '@emotion/css'; -import { getInputStyles, Icon, IconName, useStyles2, getSelectStyles } from '@grafana/ui'; +import React, { ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { getInputStyles, Icon, IconName, useStyles2, getSelectStyles } from '@grafana/ui'; export interface Props { children: ReactNode; diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index cc74304e69f..fdec6a5cb83 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useEffect, useState } from 'react'; import debounce from 'debounce-promise'; +import React, { useCallback, useEffect, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; -import { DashboardSearchHit } from 'app/features/search/types'; -import { backendSrv } from 'app/core/services/backend_srv'; import { AsyncSelectProps, AsyncSelect } from '@grafana/ui'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { DashboardSearchHit } from 'app/features/search/types'; import { DashboardDTO } from 'app/types'; interface Props diff --git a/public/app/core/components/Select/FolderPicker.test.tsx b/public/app/core/components/Select/FolderPicker.test.tsx index 43ad88e7902..2ccfdc3e467 100644 --- a/public/app/core/components/Select/FolderPicker.test.tsx +++ b/public/app/core/components/Select/FolderPicker.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + +import * as api from 'app/features/manage-dashboards/state/actions'; + +import { DashboardSearchHit } from '../../../features/search/types'; import { FolderPicker, getInitialValues } from './FolderPicker'; -import * as api from 'app/features/manage-dashboards/state/actions'; -import { DashboardSearchHit } from '../../../features/search/types'; describe('FolderPicker', () => { it('should render', () => { diff --git a/public/app/core/components/Select/FolderPicker.tsx b/public/app/core/components/Select/FolderPicker.tsx index d877ac93afb..9c74b208868 100644 --- a/public/app/core/components/Select/FolderPicker.tsx +++ b/public/app/core/components/Select/FolderPicker.tsx @@ -1,13 +1,14 @@ -import React, { PureComponent } from 'react'; import { debounce } from 'lodash'; -import { AsyncSelect } from '@grafana/ui'; +import React, { PureComponent } from 'react'; + import { AppEvents, SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; - -import appEvents from '../../app_events'; +import { AsyncSelect } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import { createFolder, getFolderById, searchFolders } from 'app/features/manage-dashboards/state/actions'; + import { AccessControlAction, PermissionLevelString } from '../../../types'; +import appEvents from '../../app_events'; export interface Props { onChange: ($folder: { title: string; id: number }) => void; diff --git a/public/app/core/components/Select/MetricSelect.test.tsx b/public/app/core/components/Select/MetricSelect.test.tsx index fef81075285..7dc5066a78b 100644 --- a/public/app/core/components/Select/MetricSelect.test.tsx +++ b/public/app/core/components/Select/MetricSelect.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { MetricSelect } from './MetricSelect'; +import React from 'react'; + import { LegacyForms } from '@grafana/ui'; + import { expect } from '../../../../test/lib/common'; +import { MetricSelect } from './MetricSelect'; + const { Select } = LegacyForms; describe('MetricSelect', () => { diff --git a/public/app/core/components/Select/MetricSelect.tsx b/public/app/core/components/Select/MetricSelect.tsx index cafc9e6d4ef..bfd0f54c88c 100644 --- a/public/app/core/components/Select/MetricSelect.tsx +++ b/public/app/core/components/Select/MetricSelect.tsx @@ -1,8 +1,8 @@ -import React, { useMemo, useCallback, FC } from 'react'; import { flatten } from 'lodash'; +import React, { useMemo, useCallback, FC } from 'react'; -import { LegacyForms } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; +import { LegacyForms } from '@grafana/ui'; import { Variable } from 'app/types/templates'; const { Select } = LegacyForms; diff --git a/public/app/core/components/Select/OrgPicker.tsx b/public/app/core/components/Select/OrgPicker.tsx index b30d24015f3..ed2c2c43524 100644 --- a/public/app/core/components/Select/OrgPicker.tsx +++ b/public/app/core/components/Select/OrgPicker.tsx @@ -1,9 +1,10 @@ import React, { useEffect } from 'react'; +import { useAsyncFn } from 'react-use'; + +import { SelectableValue } from '@grafana/data'; import { AsyncSelect } from '@grafana/ui'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { Organization } from 'app/types'; -import { SelectableValue } from '@grafana/data'; -import { useAsyncFn } from 'react-use'; export type OrgSelectItem = SelectableValue; diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx b/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx index d92d74df741..ac78be8ca21 100644 --- a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx +++ b/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.test.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import { SelectableValue } from '@grafana/data'; import { render, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { byTestId } from 'testing-library-selector'; -import * as api from './api'; -import { FolderInfo, PermissionLevelString } from '../../../../types'; -import { ALL_FOLDER, GENERAL_FOLDER, ReadonlyFolderPicker, ReadonlyFolderPickerProps } from './ReadonlyFolderPicker'; +import { SelectableValue } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; +import { FolderInfo, PermissionLevelString } from '../../../../types'; + +import { ALL_FOLDER, GENERAL_FOLDER, ReadonlyFolderPicker, ReadonlyFolderPickerProps } from './ReadonlyFolderPicker'; +import * as api from './api'; + const FOLDERS = [ { value: GENERAL_FOLDER, label: GENERAL_FOLDER.title }, { value: { id: 1, title: 'Test' }, label: 'Test' }, @@ -76,7 +78,7 @@ describe('ReadonlyFolderPicker', () => { expect(within(selectors.container.get()).getByRole('combobox')).toBeInTheDocument(); getFoldersAsOptionsSpy.mockClear(); - userEvent.type(within(selectors.container.get()).getByRole('combobox'), 'A'); + await userEvent.type(within(selectors.container.get()).getByRole('combobox'), 'A'); await waitFor(() => expect(getFoldersAsOptionsSpy).toHaveBeenCalledTimes(1)); expect(getFoldersAsOptionsSpy).toHaveBeenCalledWith({ diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx b/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx index f3c0265d813..1a9dbf764bb 100644 --- a/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx +++ b/public/app/core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker.tsx @@ -1,13 +1,15 @@ -import React, { ReactElement, useCallback, useState } from 'react'; import debouncePromise from 'debounce-promise'; +import React, { ReactElement, useCallback, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { AsyncSelect } from '@grafana/ui'; +import { GENERAL_FOLDER_ID, GENERAL_FOLDER_TITLE } from '../../../../features/search/constants'; import { FolderInfo, PermissionLevelString } from '../../../../types'; + import { findOptionWithId, getFolderAsOption, getFoldersAsOptions } from './api'; import { PermissionLevel } from './types'; -import { GENERAL_FOLDER_ID, GENERAL_FOLDER_TITLE } from '../../../../features/search/constants'; export const ALL_FOLDER: FolderInfo = { id: undefined, title: 'All' }; export const GENERAL_FOLDER: FolderInfo = { id: GENERAL_FOLDER_ID, title: GENERAL_FOLDER_TITLE }; diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts b/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts index e94176af7f5..46ab7d3adb8 100644 --- a/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts +++ b/public/app/core/components/Select/ReadonlyFolderPicker/api.test.ts @@ -1,9 +1,10 @@ +import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; import * as api from '../../../../features/manage-dashboards/state/actions'; -import { getFolderAsOption, getFoldersAsOptions } from './api'; import { DashboardSearchHit } from '../../../../features/search/types'; import { PermissionLevelString } from '../../../../types'; + import { ALL_FOLDER, GENERAL_FOLDER } from './ReadonlyFolderPicker'; -import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; +import { getFolderAsOption, getFoldersAsOptions } from './api'; function getTestContext( searchHits: DashboardSearchHit[] = [], diff --git a/public/app/core/components/Select/ReadonlyFolderPicker/api.ts b/public/app/core/components/Select/ReadonlyFolderPicker/api.ts index 06a84c4bbb4..8641fd9a6fd 100644 --- a/public/app/core/components/Select/ReadonlyFolderPicker/api.ts +++ b/public/app/core/components/Select/ReadonlyFolderPicker/api.ts @@ -1,7 +1,8 @@ import { SelectableValue } from '@grafana/data'; -import { FolderInfo, PermissionLevelString } from '../../../../types'; import { getFolderById, searchFolders } from '../../../../features/manage-dashboards/state/actions'; +import { FolderInfo, PermissionLevelString } from '../../../../types'; + import { PermissionLevel } from './types'; interface GetFoldersArgs { diff --git a/public/app/core/components/Select/SortPicker.tsx b/public/app/core/components/Select/SortPicker.tsx index e2f2a682939..98175eb0f55 100644 --- a/public/app/core/components/Select/SortPicker.tsx +++ b/public/app/core/components/Select/SortPicker.tsx @@ -1,8 +1,10 @@ import React, { FC } from 'react'; import { useAsync } from 'react-use'; -import { Icon, Select } from '@grafana/ui'; + import { SelectableValue } from '@grafana/data'; +import { Icon, Select } from '@grafana/ui'; import { DEFAULT_SORT } from 'app/features/search/constants'; + import { SearchSrv } from '../../services/search_srv'; const searchSrv = new SearchSrv(); diff --git a/public/app/core/components/Select/TeamPicker.test.tsx b/public/app/core/components/Select/TeamPicker.test.tsx index c24e25324d0..1ade476e68b 100644 --- a/public/app/core/components/Select/TeamPicker.test.tsx +++ b/public/app/core/components/Select/TeamPicker.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { TeamPicker } from './TeamPicker'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/core/components/Select/TeamPicker.tsx b/public/app/core/components/Select/TeamPicker.tsx index db17af605d4..79079c21d75 100644 --- a/public/app/core/components/Select/TeamPicker.tsx +++ b/public/app/core/components/Select/TeamPicker.tsx @@ -1,8 +1,9 @@ -import React, { Component } from 'react'; import { debounce, isNil } from 'lodash'; -import { AsyncSelect } from '@grafana/ui'; +import React, { Component } from 'react'; + import { SelectableValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; +import { AsyncSelect } from '@grafana/ui'; import { Team } from 'app/types'; export interface Props { diff --git a/public/app/core/components/Select/UserPicker.test.tsx b/public/app/core/components/Select/UserPicker.test.tsx index d63fbda5868..00efa56b10f 100644 --- a/public/app/core/components/Select/UserPicker.test.tsx +++ b/public/app/core/components/Select/UserPicker.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { UserPicker } from './UserPicker'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/core/components/Select/UserPicker.tsx b/public/app/core/components/Select/UserPicker.tsx index 4b28cb6eadf..ac09157bfe2 100644 --- a/public/app/core/components/Select/UserPicker.tsx +++ b/public/app/core/components/Select/UserPicker.tsx @@ -1,16 +1,10 @@ -// Libraries -import React, { Component } from 'react'; import { debounce, isNil } from 'lodash'; +import React, { Component } from 'react'; -// Components -import { AsyncSelect } from '@grafana/ui'; - -// Utils & Services -import { getBackendSrv } from '@grafana/runtime'; - -// Types -import { OrgUser } from 'app/types'; import { SelectableValue } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; +import { AsyncSelect } from '@grafana/ui'; +import { OrgUser } from 'app/types'; export interface Props { onSelected: (user: SelectableValue) => void; diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index c6b885c453b..0b347a3601a 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -1,6 +1,9 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; +import { t, Trans } from '@lingui/macro'; +import React, { PureComponent } from 'react'; +import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Button, Field, @@ -15,13 +18,9 @@ import { Tooltip, WeekStartPicker, } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; - -import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; -import { backendSrv } from 'app/core/services/backend_srv'; import { PreferencesService } from 'app/core/services/PreferencesService'; -import { t, Trans } from '@lingui/macro'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; export interface Props { resourceUri: string; diff --git a/public/app/core/components/Signup/SignupPage.test.tsx b/public/app/core/components/Signup/SignupPage.test.tsx index e08bc005f0a..9d3c8675632 100644 --- a/public/app/core/components/Signup/SignupPage.test.tsx +++ b/public/app/core/components/Signup/SignupPage.test.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { render } from 'test/redux-rtl'; + import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { SignupPage } from './SignupPage'; @@ -60,10 +61,10 @@ describe('Signup Page', () => { fireEvent.click(screen.getByRole('button', { name: 'Submit' })); expect(await screen.findByText('Email is required')).toBeInTheDocument(); - userEvent.type(screen.getByRole('textbox', { name: 'Email' }), 'test'); + await userEvent.type(screen.getByRole('textbox', { name: 'Email' }), 'test'); await waitFor(() => expect(screen.queryByText('Email is invalid')).toBeInTheDocument()); - userEvent.type(screen.getByRole('textbox', { name: 'Email' }), 'test@gmail.com'); + await userEvent.type(screen.getByRole('textbox', { name: 'Email' }), 'test@gmail.com'); await waitFor(() => expect(screen.queryByText('Email is invalid')).not.toBeInTheDocument()); }); it('should pass validation checks for password and confirm password field', async () => { @@ -73,11 +74,11 @@ describe('Signup Page', () => { expect(await screen.findByText('Password is required')).toBeInTheDocument(); expect(await screen.findByText('Confirmed password is required')).toBeInTheDocument(); - userEvent.type(screen.getByLabelText('Password'), 'admin'); - userEvent.type(screen.getByLabelText('Confirm password'), 'a'); + await userEvent.type(screen.getByLabelText('Password'), 'admin'); + await userEvent.type(screen.getByLabelText('Confirm password'), 'a'); await waitFor(() => expect(screen.queryByText('Passwords must match!')).toBeInTheDocument()); - userEvent.type(screen.getByLabelText('Confirm password'), 'dmin'); + await userEvent.type(screen.getByLabelText('Confirm password'), 'dmin'); await waitFor(() => expect(screen.queryByText('Passwords must match!')).not.toBeInTheDocument()); }); it('should navigate to default url if signup is successful', async () => { @@ -89,10 +90,10 @@ describe('Signup Page', () => { postMock.mockResolvedValueOnce({ message: 'Logged in' }); render(); - userEvent.type(screen.getByRole('textbox', { name: 'Your name' }), 'test-user'); - userEvent.type(screen.getByRole('textbox', { name: 'Email' }), 'test@gmail.com'); - userEvent.type(screen.getByLabelText('Password'), 'admin'); - userEvent.type(screen.getByLabelText('Confirm password'), 'admin'); + await userEvent.type(screen.getByRole('textbox', { name: 'Your name' }), 'test-user'); + await userEvent.type(screen.getByRole('textbox', { name: 'Email' }), 'test@gmail.com'); + await userEvent.type(screen.getByLabelText('Password'), 'admin'); + await userEvent.type(screen.getByLabelText('Confirm password'), 'admin'); fireEvent.click(screen.getByRole('button', { name: 'Submit' })); await waitFor(() => diff --git a/public/app/core/components/Signup/SignupPage.tsx b/public/app/core/components/Signup/SignupPage.tsx index 4eab139d56b..3d6a6188475 100644 --- a/public/app/core/components/Signup/SignupPage.tsx +++ b/public/app/core/components/Signup/SignupPage.tsx @@ -1,9 +1,11 @@ import React, { FC } from 'react'; -import { Form, Field, Input, Button, HorizontalGroup, LinkButton, FormAPI } from '@grafana/ui'; + import { getBackendSrv } from '@grafana/runtime'; +import { Form, Field, Input, Button, HorizontalGroup, LinkButton, FormAPI } from '@grafana/ui'; import { getConfig } from 'app/core/config'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { useAppNotification } from 'app/core/copy/appNotification'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; + import { InnerBox, LoginLayout } from '../Login/LoginLayout'; import { PasswordField } from '../PasswordField/PasswordField'; diff --git a/public/app/core/components/Signup/VerifyEmail.tsx b/public/app/core/components/Signup/VerifyEmail.tsx index ff49e1e8ff2..81eab2cc66b 100644 --- a/public/app/core/components/Signup/VerifyEmail.tsx +++ b/public/app/core/components/Signup/VerifyEmail.tsx @@ -1,6 +1,7 @@ import React, { FC, useState } from 'react'; -import { Form, Field, Input, Button, Legend, Container, HorizontalGroup, LinkButton } from '@grafana/ui'; + import { getBackendSrv } from '@grafana/runtime'; +import { Form, Field, Input, Button, Legend, Container, HorizontalGroup, LinkButton } from '@grafana/ui'; import { getConfig } from 'app/core/config'; import { useAppNotification } from 'app/core/copy/appNotification'; diff --git a/public/app/core/components/Signup/VerifyEmailPage.test.tsx b/public/app/core/components/Signup/VerifyEmailPage.test.tsx index f03435676b7..00dbb378a4d 100644 --- a/public/app/core/components/Signup/VerifyEmailPage.test.tsx +++ b/public/app/core/components/Signup/VerifyEmailPage.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; import { fireEvent, screen, waitFor } from '@testing-library/react'; -import { render } from 'test/redux-rtl'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { render } from 'test/redux-rtl'; import { VerifyEmailPage } from './VerifyEmailPage'; @@ -44,17 +44,17 @@ describe('VerifyEmail Page', () => { fireEvent.click(screen.getByRole('button', { name: 'Send verification email' })); expect(await screen.findByText('Email is required')).toBeInTheDocument(); - userEvent.type(screen.getByRole('textbox', { name: /Email/i }), 'test'); + await userEvent.type(screen.getByRole('textbox', { name: /Email/i }), 'test'); await waitFor(() => expect(screen.queryByText('Email is invalid')).toBeInTheDocument()); - userEvent.type(screen.getByRole('textbox', { name: /Email/i }), 'test@gmail.com'); + await userEvent.type(screen.getByRole('textbox', { name: /Email/i }), 'test@gmail.com'); await waitFor(() => expect(screen.queryByText('Email is invalid')).not.toBeInTheDocument()); }); it('should show complete signup if email-verification is successful', async () => { postMock.mockResolvedValueOnce({ message: 'SignUpCreated' }); render(); - userEvent.type(screen.getByRole('textbox', { name: /Email/i }), 'test@gmail.com'); + await userEvent.type(screen.getByRole('textbox', { name: /Email/i }), 'test@gmail.com'); fireEvent.click(screen.getByRole('button', { name: 'Send verification email' })); await waitFor(() => diff --git a/public/app/core/components/Signup/VerifyEmailPage.tsx b/public/app/core/components/Signup/VerifyEmailPage.tsx index 3583fca4924..8a3eacd13e1 100644 --- a/public/app/core/components/Signup/VerifyEmailPage.tsx +++ b/public/app/core/components/Signup/VerifyEmailPage.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; import { LoginLayout, InnerBox } from '../Login/LoginLayout'; + import { VerifyEmail } from './VerifyEmail'; export const VerifyEmailPage: FC = () => { diff --git a/public/app/core/components/SplitPaneWrapper/SplitPaneWrapper.tsx b/public/app/core/components/SplitPaneWrapper/SplitPaneWrapper.tsx index 4efc15bc8da..09f53bf7f32 100644 --- a/public/app/core/components/SplitPaneWrapper/SplitPaneWrapper.tsx +++ b/public/app/core/components/SplitPaneWrapper/SplitPaneWrapper.tsx @@ -1,6 +1,7 @@ +import { css, cx } from '@emotion/css'; import React, { createRef, MutableRefObject, PureComponent, ReactNode } from 'react'; import SplitPane from 'react-split-pane'; -import { css, cx } from '@emotion/css'; + import { GrafanaTheme } from '@grafana/data'; import { stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; diff --git a/public/app/core/components/TagFilter/TagBadge.tsx b/public/app/core/components/TagFilter/TagBadge.tsx index 1d071464ff4..fa184f0b9f3 100644 --- a/public/app/core/components/TagFilter/TagBadge.tsx +++ b/public/app/core/components/TagFilter/TagBadge.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { getTagColorsFromName, Icon } from '@grafana/ui'; export interface Props { diff --git a/public/app/core/components/TagFilter/TagFilter.tsx b/public/app/core/components/TagFilter/TagFilter.tsx index c419f6a8db7..ed262ec9974 100644 --- a/public/app/core/components/TagFilter/TagFilter.tsx +++ b/public/app/core/components/TagFilter/TagFilter.tsx @@ -1,11 +1,12 @@ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { components } from 'react-select'; -import { Icon, MultiSelect, useStyles2 } from '@grafana/ui'; -import { escapeStringForRegex, GrafanaTheme2 } from '@grafana/data'; -import { TagOption } from './TagOption'; +import { escapeStringForRegex, GrafanaTheme2 } from '@grafana/data'; +import { Icon, MultiSelect, useStyles2 } from '@grafana/ui'; + import { TagBadge } from './TagBadge'; +import { TagOption } from './TagOption'; export interface TermCount { term: string; diff --git a/public/app/core/components/TagFilter/TagOption.tsx b/public/app/core/components/TagFilter/TagOption.tsx index 3505e958764..c7ad15fcb18 100644 --- a/public/app/core/components/TagFilter/TagOption.tsx +++ b/public/app/core/components/TagFilter/TagOption.tsx @@ -1,9 +1,10 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; -import { useTheme, stylesFactory } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; - +import React, { FC } from 'react'; import { OptionProps } from 'react-select'; + +import { GrafanaTheme } from '@grafana/data'; +import { useTheme, stylesFactory } from '@grafana/ui'; + import { TagBadge } from './TagBadge'; // https://github.com/JedWatson/react-select/issues/3038 diff --git a/public/app/core/components/TagFilter/TagValue.tsx b/public/app/core/components/TagFilter/TagValue.tsx index 0406ead16a6..530e43dcc12 100644 --- a/public/app/core/components/TagFilter/TagValue.tsx +++ b/public/app/core/components/TagFilter/TagValue.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { TagBadge } from './TagBadge'; export interface Props { diff --git a/public/app/core/components/TimePicker/TimePickerWithHistory.tsx b/public/app/core/components/TimePicker/TimePickerWithHistory.tsx index 2306922ba70..85ccd92df81 100644 --- a/public/app/core/components/TimePicker/TimePickerWithHistory.tsx +++ b/public/app/core/components/TimePicker/TimePickerWithHistory.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import { LocalStorageValueProvider } from '../LocalStorageValueProvider'; + import { TimeRange, isDateTime, toUtc } from '@grafana/data'; import { TimeRangePickerProps, TimeRangePicker } from '@grafana/ui'; +import { LocalStorageValueProvider } from '../LocalStorageValueProvider'; + const LOCAL_STORAGE_KEY = 'grafana.dashboard.timepicker.history'; interface Props extends Omit {} diff --git a/public/app/core/components/TraceToLogs/KeyValueInput.tsx b/public/app/core/components/TraceToLogs/KeyValueInput.tsx index 40258a93874..45934073fdb 100644 --- a/public/app/core/components/TraceToLogs/KeyValueInput.tsx +++ b/public/app/core/components/TraceToLogs/KeyValueInput.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme, KeyValue } from '@grafana/data'; import { SegmentInput, useStyles, InlineLabel, Icon } from '@grafana/ui'; -import React from 'react'; const EQ_WIDTH = 3; // = 24px in inline label diff --git a/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx b/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx index 871a967f2bf..9df3adefe7a 100644 --- a/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx +++ b/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx @@ -1,4 +1,6 @@ import { css } from '@emotion/css'; +import React from 'react'; + import { DataSourceJsonData, DataSourceInstanceSettings, @@ -9,7 +11,7 @@ import { } from '@grafana/data'; import { DataSourcePicker } from '@grafana/runtime'; import { InlineField, InlineFieldRow, Input, TagsInput, useStyles, InlineSwitch } from '@grafana/ui'; -import React from 'react'; + import KeyValueInput from './KeyValueInput'; export interface TraceToLogsOptions { diff --git a/public/app/core/components/Upgrade/ProBadge.tsx b/public/app/core/components/Upgrade/ProBadge.tsx index 27b92f9279f..c886cc7e295 100644 --- a/public/app/core/components/Upgrade/ProBadge.tsx +++ b/public/app/core/components/Upgrade/ProBadge.tsx @@ -1,22 +1,24 @@ -import React, { HTMLAttributes, useEffect } from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; +import React, { HTMLAttributes, useEffect } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { reportExperimentView } from '@grafana/runtime'; +import { useStyles2 } from '@grafana/ui'; export interface Props extends HTMLAttributes { text?: string; experimentId?: string; + eventVariant?: string; } -export const ProBadge = ({ text = 'PRO', className, experimentId, ...htmlProps }: Props) => { +export const ProBadge = ({ text = 'PRO', className, experimentId, eventVariant = '', ...htmlProps }: Props) => { const styles = useStyles2(getStyles); useEffect(() => { if (experimentId) { - reportExperimentView(experimentId, 'test', ''); + reportExperimentView(experimentId, 'test', eventVariant); } - }, [experimentId]); + }, [experimentId, eventVariant]); return ( diff --git a/public/app/core/components/Upgrade/UpgradeBox.tsx b/public/app/core/components/Upgrade/UpgradeBox.tsx index 7b1efb2f929..c4b1abb9912 100644 --- a/public/app/core/components/Upgrade/UpgradeBox.tsx +++ b/public/app/core/components/Upgrade/UpgradeBox.tsx @@ -1,24 +1,42 @@ -import React, { HTMLAttributes } from 'react'; import { css, cx } from '@emotion/css'; -import { Icon, LinkButton, useStyles2 } from '@grafana/ui'; +import React, { HTMLAttributes, useEffect } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { reportExperimentView } from '@grafana/runtime/src'; +import { Button, Icon, LinkButton, useStyles2 } from '@grafana/ui'; type ComponentSize = 'sm' | 'md'; export interface Props extends HTMLAttributes { featureName: string; size?: ComponentSize; + text?: string; + eventVariant?: string; + featureId: string; } -export const UpgradeBox = ({ featureName, className, children, size = 'md', ...htmlProps }: Props) => { +export const UpgradeBox = ({ + featureName, + className, + children, + text, + featureId, + eventVariant = '', + size = 'md', + ...htmlProps +}: Props) => { const styles = useStyles2((theme) => getUpgradeBoxStyles(theme, size)); + useEffect(() => { + reportExperimentView(`feature-highlights-${featureId}`, 'test', eventVariant); + }, [eventVariant, featureId]); + return (

    - You’ve discovered a Pro feature! Get the Grafana Pro plan to access {featureName}. + You’ve discovered a Pro feature! {text || `Get the Grafana Pro plan to access ${featureName}.`}

    void; + }; } export const UpgradeContent = ({ @@ -101,6 +124,7 @@ export const UpgradeContent = ({ featureName, description, caption, + action, }: UpgradeContentProps) => { const styles = useStyles2(getUpgradeContentStyles); return ( @@ -115,6 +139,16 @@ export const UpgradeContent = ({ ))}
+ {action?.link && ( + + {action.text} + + )} + {action?.onClick && ( + + )} {featureUrl && ( Learn more @@ -146,6 +180,9 @@ const getUpgradeContentStyles = (theme: GrafanaTheme2) => { width: 100%; } `, + title: css` + color: ${theme.colors.text.maxContrast}; + `, description: css` color: ${theme.colors.text.primary}; font-weight: ${theme.typography.fontWeightLight}; @@ -168,9 +205,6 @@ const getUpgradeContentStyles = (theme: GrafanaTheme2) => { link: css` margin-left: ${theme.spacing(2)}; `, - title: css` - color: ${theme.colors.text.maxContrast}; - `, caption: css` font-weight: ${theme.typography.fontWeightLight}; margin: ${theme.spacing(1, 0, 0)}; diff --git a/public/app/core/components/connectWithCleanUp.tsx b/public/app/core/components/connectWithCleanUp.tsx index 712d4e8c5b8..6c1cc95ab6b 100644 --- a/public/app/core/components/connectWithCleanUp.tsx +++ b/public/app/core/components/connectWithCleanUp.tsx @@ -1,7 +1,8 @@ -import { connect, MapDispatchToPropsParam, MapStateToPropsParam, useDispatch } from 'react-redux'; -import { cleanUpAction, StateSelector } from '../actions/cleanUp'; -import React, { ComponentType, FunctionComponent, useEffect } from 'react'; import hoistNonReactStatics from 'hoist-non-react-statics'; +import React, { ComponentType, FunctionComponent, useEffect } from 'react'; +import { connect, MapDispatchToPropsParam, MapStateToPropsParam, useDispatch } from 'react-redux'; + +import { cleanUpAction, StateSelector } from '../actions/cleanUp'; export const connectWithCleanUp = < diff --git a/public/app/core/components/editors/DashboardPicker.tsx b/public/app/core/components/editors/DashboardPicker.tsx index 51192d9c0f8..2baaf4d46d4 100644 --- a/public/app/core/components/editors/DashboardPicker.tsx +++ b/public/app/core/components/editors/DashboardPicker.tsx @@ -1,4 +1,5 @@ import React, { FC, useCallback } from 'react'; + import { SelectableValue, StandardEditorProps } from '@grafana/data'; import { DashboardPicker as BasePicker, DashboardPickerDTO } from 'app/core/components/Select/DashboardPicker'; diff --git a/public/app/core/components/editors/DashboardPickerByID.tsx b/public/app/core/components/editors/DashboardPickerByID.tsx index c79ef4f4b8c..7bcc9bee8fb 100644 --- a/public/app/core/components/editors/DashboardPickerByID.tsx +++ b/public/app/core/components/editors/DashboardPickerByID.tsx @@ -1,8 +1,9 @@ -import React, { FC } from 'react'; import debounce from 'debounce-promise'; +import React, { FC } from 'react'; + +import { SelectableValue } from '@grafana/data'; import { AsyncSelect } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; -import { SelectableValue } from '@grafana/data'; /** * @deprecated prefer using dashboard uid rather than id diff --git a/public/app/core/components/editors/registry.tsx b/public/app/core/components/editors/registry.tsx index d9e80595205..19220e2a35b 100644 --- a/public/app/core/components/editors/registry.tsx +++ b/public/app/core/components/editors/registry.tsx @@ -1,5 +1,3 @@ -import { DashboardPicker, DashboardPickerOptions } from './DashboardPicker'; -import { getStandardFieldConfigs, getStandardOptionEditors } from '@grafana/ui'; import { FieldConfigPropertyItem, FieldType, @@ -13,8 +11,11 @@ import { ValueMappingFieldConfigSettings, valueMappingsOverrideProcessor, } from '@grafana/data'; -import { ValueMappingsEditor } from 'app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor'; +import { getStandardFieldConfigs, getStandardOptionEditors } from '@grafana/ui'; import { ThresholdsValueEditor } from 'app/features/dimensions/editors/ThresholdsEditor/thresholds'; +import { ValueMappingsEditor } from 'app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor'; + +import { DashboardPicker, DashboardPickerOptions } from './DashboardPicker'; /** * Returns collection of standard option editors definitions diff --git a/public/app/core/components/help/HelpModal.tsx b/public/app/core/components/help/HelpModal.tsx index 70d9fda91e8..7722dbc2dcf 100644 --- a/public/app/core/components/help/HelpModal.tsx +++ b/public/app/core/components/help/HelpModal.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Modal, useStyles2 } from '@grafana/ui'; diff --git a/public/app/core/components/modals/AngularModalProxy.tsx b/public/app/core/components/modals/AngularModalProxy.tsx index 70ed905b4c3..2231f856ef4 100644 --- a/public/app/core/components/modals/AngularModalProxy.tsx +++ b/public/app/core/components/modals/AngularModalProxy.tsx @@ -1,7 +1,9 @@ -import { connectWithProvider } from '../../utils/connectWithReduxStore'; -import { ModalRoot, ModalsProvider } from '@grafana/ui'; import React from 'react'; +import { ModalRoot, ModalsProvider } from '@grafana/ui'; + +import { connectWithProvider } from '../../utils/connectWithReduxStore'; + /** * Component that enables rendering React modals from Angular */ diff --git a/public/app/core/config.ts b/public/app/core/config.ts index fcd399c9d30..db165baf96b 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,5 +1,5 @@ -import { config, GrafanaBootConfig } from '@grafana/runtime'; import { PluginState } from '@grafana/data'; +import { config, GrafanaBootConfig } from '@grafana/runtime'; // Legacy binding paths export { config, GrafanaBootConfig as Settings }; diff --git a/public/app/core/copy/appNotification.ts b/public/app/core/copy/appNotification.ts index 2c94b701653..485cdc6d4c6 100644 --- a/public/app/core/copy/appNotification.ts +++ b/public/app/core/copy/appNotification.ts @@ -1,7 +1,9 @@ import { useMemo } from 'react'; -import { AppNotification, AppNotificationSeverity, AppNotificationTimeout, useDispatch } from 'app/types'; -import { getMessageFromError } from 'app/core/utils/errors'; import { v4 as uuidv4 } from 'uuid'; + +import { getMessageFromError } from 'app/core/utils/errors'; +import { AppNotification, AppNotificationSeverity, useDispatch } from 'app/types'; + import { notifyApp } from '../actions'; const defaultSuccessNotification = { @@ -9,7 +11,6 @@ const defaultSuccessNotification = { text: '', severity: AppNotificationSeverity.Success, icon: 'check', - timeout: AppNotificationTimeout.Success, }; const defaultWarningNotification = { @@ -17,7 +18,6 @@ const defaultWarningNotification = { text: '', severity: AppNotificationSeverity.Warning, icon: 'exclamation-triangle', - timeout: AppNotificationTimeout.Warning, }; const defaultErrorNotification = { @@ -25,19 +25,21 @@ const defaultErrorNotification = { text: '', severity: AppNotificationSeverity.Error, icon: 'exclamation-triangle', - timeout: AppNotificationTimeout.Error, }; -export const createSuccessNotification = (title: string, text = ''): AppNotification => ({ +export const createSuccessNotification = (title: string, text = '', traceId?: string): AppNotification => ({ ...defaultSuccessNotification, - title: title, - text: text, + title, + text, id: uuidv4(), + timestamp: Date.now(), + showing: true, }); export const createErrorNotification = ( title: string, text: string | Error = '', + traceId?: string, component?: React.ReactElement ): AppNotification => { return { @@ -45,15 +47,21 @@ export const createErrorNotification = ( text: getMessageFromError(text), title, id: uuidv4(), + traceId, component, + timestamp: Date.now(), + showing: true, }; }; -export const createWarningNotification = (title: string, text = ''): AppNotification => ({ +export const createWarningNotification = (title: string, text = '', traceId?: string): AppNotification => ({ ...defaultWarningNotification, - title: title, - text: text, + title, + text, + traceId, id: uuidv4(), + timestamp: Date.now(), + showing: true, }); /** Hook for showing toast notifications with varying severity (success, warning error). @@ -70,11 +78,11 @@ export function useAppNotification() { success: (title: string, text = '') => { dispatch(notifyApp(createSuccessNotification(title, text))); }, - warning: (title: string, text = '') => { - dispatch(notifyApp(createWarningNotification(title, text))); + warning: (title: string, text = '', traceId?: string) => { + dispatch(notifyApp(createWarningNotification(title, text, traceId))); }, - error: (title: string, text = '') => { - dispatch(notifyApp(createErrorNotification(title, text))); + error: (title: string, text = '', traceId?: string) => { + dispatch(notifyApp(createErrorNotification(title, text, traceId))); }, }), [dispatch] diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 38e3808619d..0833d4a58f4 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -1,10 +1,11 @@ import { colors, JsonExplorer } from '@grafana/ui/'; + import appEvents from './app_events'; -import { assignModelProperties } from './utils/model_utils'; +import { profiler } from './profiler'; import { contextSrv } from './services/context_srv'; import { KeybindingSrv } from './services/keybindingSrv'; -import { profiler } from './profiler'; import TimeSeries, { updateLegendValues } from './time_series2'; +import { assignModelProperties } from './utils/model_utils'; export { profiler, diff --git a/public/app/core/history/RichHistoryLocalStorage.test.ts b/public/app/core/history/RichHistoryLocalStorage.test.ts index 747b6720935..c587c07a927 100644 --- a/public/app/core/history/RichHistoryLocalStorage.test.ts +++ b/public/app/core/history/RichHistoryLocalStorage.test.ts @@ -1,12 +1,14 @@ -import RichHistoryLocalStorage, { MAX_HISTORY_ITEMS } from './RichHistoryLocalStorage'; -import store from 'app/core/store'; -import { RichHistoryQuery } from '../../types'; import { DataQuery } from '@grafana/data'; +import store from 'app/core/store'; + import { afterEach, beforeEach } from '../../../test/lib/common'; -import { RichHistoryStorageWarning } from './RichHistoryStorage'; +import { RichHistoryQuery } from '../../types'; import { backendSrv } from '../services/backend_srv'; import { RichHistorySettings } from '../utils/richHistoryTypes'; +import RichHistoryLocalStorage, { MAX_HISTORY_ITEMS } from './RichHistoryLocalStorage'; +import { RichHistoryStorageWarning } from './RichHistoryStorage'; + const key = 'grafana.explore.richHistory'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/core/history/RichHistoryLocalStorage.ts b/public/app/core/history/RichHistoryLocalStorage.ts index ca98b0c6a45..44e943e6468 100644 --- a/public/app/core/history/RichHistoryLocalStorage.ts +++ b/public/app/core/history/RichHistoryLocalStorage.ts @@ -1,12 +1,15 @@ -import RichHistoryStorage, { RichHistoryServiceError, RichHistoryStorageWarning } from './RichHistoryStorage'; +import { find, isEqual, omit } from 'lodash'; + +import { DataQuery } from '@grafana/data'; + import { RichHistoryQuery } from '../../types'; import store from '../store'; -import { DataQuery } from '@grafana/data'; -import { find, isEqual, omit } from 'lodash'; -import { createRetentionPeriodBoundary, RICH_HISTORY_SETTING_KEYS } from './richHistoryLocalStorageUtils'; -import { fromDTO, toDTO } from './localStorageConverter'; import { RichHistorySettings } from '../utils/richHistoryTypes'; +import RichHistoryStorage, { RichHistoryServiceError, RichHistoryStorageWarning } from './RichHistoryStorage'; +import { fromDTO, toDTO } from './localStorageConverter'; +import { createRetentionPeriodBoundary, RICH_HISTORY_SETTING_KEYS } from './richHistoryLocalStorageUtils'; + export const RICH_HISTORY_KEY = 'grafana.explore.richHistory'; export const MAX_HISTORY_ITEMS = 10000; diff --git a/public/app/core/history/localStorageConverter.test.ts b/public/app/core/history/localStorageConverter.test.ts index 5a18d27747e..9a7e7ff4cd4 100644 --- a/public/app/core/history/localStorageConverter.test.ts +++ b/public/app/core/history/localStorageConverter.test.ts @@ -1,7 +1,8 @@ -import { backendSrv } from '../services/backend_srv'; -import { fromDTO, toDTO } from './localStorageConverter'; import { RichHistoryQuery } from '../../types'; +import { backendSrv } from '../services/backend_srv'; + import { RichHistoryLocalStorageDTO } from './RichHistoryLocalStorage'; +import { fromDTO, toDTO } from './localStorageConverter'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/core/history/localStorageConverter.ts b/public/app/core/history/localStorageConverter.ts index e8569969449..8e480ffc8d6 100644 --- a/public/app/core/history/localStorageConverter.ts +++ b/public/app/core/history/localStorageConverter.ts @@ -1,9 +1,12 @@ import { find } from 'lodash'; + import { DataSourceInstanceSettings } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; -import { RichHistoryLocalStorageDTO } from './RichHistoryLocalStorage'; + import { RichHistoryQuery } from '../../types'; +import { RichHistoryLocalStorageDTO } from './RichHistoryLocalStorage'; + export const fromDTO = (dto: RichHistoryLocalStorageDTO): RichHistoryQuery => { const datasource = find( getDataSourceSrv().getList(), diff --git a/public/app/core/history/richHistoryLocalStorageUtils.ts b/public/app/core/history/richHistoryLocalStorageUtils.ts index 67b644f496c..096b366da50 100644 --- a/public/app/core/history/richHistoryLocalStorageUtils.ts +++ b/public/app/core/history/richHistoryLocalStorageUtils.ts @@ -1,5 +1,6 @@ -import { RichHistoryQuery } from '../../types'; import { omit } from 'lodash'; + +import { RichHistoryQuery } from '../../types'; import { SortOrder } from '../utils/richHistoryTypes'; /** diff --git a/public/app/core/hooks/useCleanup.ts b/public/app/core/hooks/useCleanup.ts index b7940c9d474..33b16150cbf 100644 --- a/public/app/core/hooks/useCleanup.ts +++ b/public/app/core/hooks/useCleanup.ts @@ -1,5 +1,6 @@ import { useEffect, useRef } from 'react'; import { useDispatch } from 'react-redux'; + import { cleanUpAction, StateSelector } from '../actions/cleanUp'; export function useCleanup(stateSelector: StateSelector) { diff --git a/public/app/core/hooks/useNavModel.ts b/public/app/core/hooks/useNavModel.ts index 18bb07d6c8b..99917e14262 100644 --- a/public/app/core/hooks/useNavModel.ts +++ b/public/app/core/hooks/useNavModel.ts @@ -1,7 +1,9 @@ import { useSelector } from 'react-redux'; -import { StoreState } from 'app/types/store'; -import { getNavModel } from '../selectors/navModel'; + import { NavModel } from '@grafana/data'; +import { StoreState } from 'app/types/store'; + +import { getNavModel } from '../selectors/navModel'; export const useNavModel = (id: string): NavModel => { const navIndex = useSelector((state: StoreState) => state.navIndex); diff --git a/public/app/core/hooks/useQueryParams.ts b/public/app/core/hooks/useQueryParams.ts index 8ee53b8fcd2..907f039e1e5 100644 --- a/public/app/core/hooks/useQueryParams.ts +++ b/public/app/core/hooks/useQueryParams.ts @@ -1,8 +1,9 @@ -import { UrlQueryMap } from '@grafana/data'; -import { locationSearchToObject, locationService } from '@grafana/runtime'; import { useCallback, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; +import { UrlQueryMap } from '@grafana/data'; +import { locationSearchToObject, locationService } from '@grafana/runtime'; + export function useQueryParams(): [UrlQueryMap, (values: UrlQueryMap, replace?: boolean) => void] { const { search } = useLocation(); const queryParams = useMemo(() => locationSearchToObject(search || ''), [search]); diff --git a/public/app/core/localisation.tsx b/public/app/core/localisation.tsx index f979e61c4e8..c7626b68698 100644 --- a/public/app/core/localisation.tsx +++ b/public/app/core/localisation.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; import { I18n, i18n } from '@lingui/core'; import { I18nProvider as LinguiI18nProvider } from '@lingui/react'; +import React, { useState } from 'react'; import { messages } from '../../locales/en/messages'; diff --git a/public/app/core/logs_model.test.ts b/public/app/core/logs_model.test.ts index e09b6205849..34ffa4d5671 100644 --- a/public/app/core/logs_model.test.ts +++ b/public/app/core/logs_model.test.ts @@ -1,3 +1,5 @@ +import { Observable } from 'rxjs'; + import { ArrayVector, DataFrame, @@ -14,6 +16,9 @@ import { MutableDataFrame, toDataFrame, } from '@grafana/data'; + +import { MockObservableDataSourceApi } from '../../test/mocks/datasource_srv'; + import { COMMON_LABELS, dataFrameToLogsModel, @@ -24,8 +29,6 @@ import { logSeriesToLogsModel, queryLogsVolume, } from './logs_model'; -import { Observable } from 'rxjs'; -import { MockObservableDataSourceApi } from '../../test/mocks/datasource_srv'; describe('dedupLogRows()', () => { test('should return rows as is when dedup is set to none', () => { diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 36316d6a85e..bde1bd525a0 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -1,6 +1,5 @@ import { size } from 'lodash'; -import { BarAlignment, GraphDrawStyle, StackingMode } from '@grafana/schema'; -import { ansicolor, colors } from '@grafana/ui'; +import { Observable } from 'rxjs'; import { AbsoluteTimeRange, @@ -37,9 +36,10 @@ import { toDataFrame, toUtc, } from '@grafana/data'; -import { getThemeColor } from 'app/core/utils/colors'; import { SIPrefix } from '@grafana/data/src/valueFormats/symbolFormatters'; -import { Observable } from 'rxjs'; +import { BarAlignment, GraphDrawStyle, StackingMode } from '@grafana/schema'; +import { ansicolor, colors } from '@grafana/ui'; +import { getThemeColor } from 'app/core/utils/colors'; export const LIMIT_LABEL = 'Line limit'; export const COMMON_LABELS = 'Common labels'; diff --git a/public/app/core/navigation/GrafanaRoute.test.tsx b/public/app/core/navigation/GrafanaRoute.test.tsx index 6b434645d25..514e38dbf45 100644 --- a/public/app/core/navigation/GrafanaRoute.test.tsx +++ b/public/app/core/navigation/GrafanaRoute.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import { GrafanaRoute } from './GrafanaRoute'; +import React from 'react'; + import { setEchoSrv } from '@grafana/runtime'; + import { Echo } from '../services/echo/Echo'; +import { GrafanaRoute } from './GrafanaRoute'; + describe('GrafanaRoute', () => { beforeEach(() => { setEchoSrv(new Echo()); diff --git a/public/app/core/navigation/GrafanaRoute.tsx b/public/app/core/navigation/GrafanaRoute.tsx index 500218812cb..3ff44bc54bf 100644 --- a/public/app/core/navigation/GrafanaRoute.tsx +++ b/public/app/core/navigation/GrafanaRoute.tsx @@ -1,10 +1,13 @@ import React from 'react'; // @ts-ignore import Drop from 'tether-drop'; -import { GrafanaRouteComponentProps } from './types'; + import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime'; + import { keybindingSrv } from '../services/keybindingSrv'; +import { GrafanaRouteComponentProps } from './types'; + export interface Props extends Omit {} export class GrafanaRoute extends React.Component { diff --git a/public/app/core/navigation/RouterDebugger.tsx b/public/app/core/navigation/RouterDebugger.tsx index 2a177379f26..49d621ea7f9 100644 --- a/public/app/core/navigation/RouterDebugger.tsx +++ b/public/app/core/navigation/RouterDebugger.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Link } from 'react-router-dom'; + import { getAppRoutes } from '../../routes/routes'; import { PageContents } from '../components/Page/PageContents'; + import { RouteDescriptor } from './types'; export const RouterDebugger: React.FC = () => { diff --git a/public/app/core/navigation/__mocks__/routeProps.ts b/public/app/core/navigation/__mocks__/routeProps.ts index 6c3ff25ecd8..0b22e1d2449 100644 --- a/public/app/core/navigation/__mocks__/routeProps.ts +++ b/public/app/core/navigation/__mocks__/routeProps.ts @@ -1,7 +1,8 @@ -import { GrafanaRouteComponentProps } from '../types'; import { createMemoryHistory } from 'history'; import { merge } from 'lodash'; +import { GrafanaRouteComponentProps } from '../types'; + export function getRouteComponentProps = {}>( overrides: Partial = {} ): GrafanaRouteComponentProps { diff --git a/public/app/core/navigation/hooks.ts b/public/app/core/navigation/hooks.ts index 5c30ac06688..566c5e3f50e 100644 --- a/public/app/core/navigation/hooks.ts +++ b/public/app/core/navigation/hooks.ts @@ -1,6 +1,7 @@ -import { locationService } from '@grafana/runtime'; import { useLocation } from 'react-router-dom'; +import { locationService } from '@grafana/runtime'; + export type UseUrlParamsResult = [URLSearchParams, (params: Record, replace?: boolean) => void]; /** @internal experimental */ diff --git a/public/app/core/navigation/kiosk.ts b/public/app/core/navigation/kiosk.ts index b041d1f5272..61f36656c61 100644 --- a/public/app/core/navigation/kiosk.ts +++ b/public/app/core/navigation/kiosk.ts @@ -1,7 +1,8 @@ import { AppEvents } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import appEvents from '../app_events'; + import { KioskMode } from '../../types'; +import appEvents from '../app_events'; export function toggleKioskMode() { let kiosk = locationService.getSearchObject().kiosk; diff --git a/public/app/core/navigation/patch/interceptLinkClicks.ts b/public/app/core/navigation/patch/interceptLinkClicks.ts index f7381447e12..377a3413ab0 100644 --- a/public/app/core/navigation/patch/interceptLinkClicks.ts +++ b/public/app/core/navigation/patch/interceptLinkClicks.ts @@ -27,6 +27,10 @@ export function interceptLinkClicks(e: MouseEvent) { if (href.indexOf('://') > 0 || href.indexOf('mailto:') === 0) { window.location.href = href; return; + } else if (href.indexOf('#') === 0) { + // If it is a hash click, update the hash instead of trying to update the history + window.location.hash = href; + return; } else { href = `/${href}`; } diff --git a/public/app/core/navigation/testRoutes.tsx b/public/app/core/navigation/testRoutes.tsx index c7087d16485..6a5dc26e442 100644 --- a/public/app/core/navigation/testRoutes.tsx +++ b/public/app/core/navigation/testRoutes.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Link, NavLink } from 'react-router-dom'; + import { RouterDebugger } from './RouterDebugger'; import { RouteDescriptor } from './types'; diff --git a/public/app/core/navigation/types.ts b/public/app/core/navigation/types.ts index a88d082a297..aed3b16a62f 100644 --- a/public/app/core/navigation/types.ts +++ b/public/app/core/navigation/types.ts @@ -1,7 +1,8 @@ -import { UrlQueryMap } from '@grafana/data'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; +import { UrlQueryMap } from '@grafana/data'; + export interface GrafanaRouteComponentProps extends RouteComponentProps { route: RouteDescriptor; queryParams: Q; diff --git a/public/app/core/reducers/appNotification.test.ts b/public/app/core/reducers/appNotification.test.ts index edbf431ba2e..33a12aa5f46 100644 --- a/public/app/core/reducers/appNotification.test.ts +++ b/public/app/core/reducers/appNotification.test.ts @@ -1,6 +1,8 @@ -import { appNotificationsReducer, clearAppNotification, notifyApp } from './appNotification'; -import { AppNotificationSeverity, AppNotificationsState, AppNotificationTimeout } from 'app/types/'; +import { AppNotificationSeverity, AppNotificationsState } from 'app/types/'; +import { appNotificationsReducer, clearNotification, notifyApp } from './appNotification'; + +const timestamp = 1649849468889; describe('clear alert', () => { it('should filter alert', () => { const id1 = '1767d3d9-4b99-40eb-ab46-de734a66f21d'; @@ -14,7 +16,8 @@ describe('clear alert', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp, }, [id2]: { id: id2, @@ -22,12 +25,14 @@ describe('clear alert', () => { icon: 'warning', title: 'test2', text: 'test alert fail 2', - timeout: AppNotificationTimeout.Warning, + showing: true, + timestamp, }, }, + lastRead: timestamp - 10, }; - const result = appNotificationsReducer(initialState, clearAppNotification(id2)); + const result = appNotificationsReducer(initialState, clearNotification(id2)); const expectedResult: AppNotificationsState = { byId: { @@ -37,9 +42,11 @@ describe('clear alert', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp, }, }, + lastRead: timestamp - 10, }; expect(result).toEqual(expectedResult); @@ -60,7 +67,8 @@ describe('notify', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp, }, [id2]: { id: id2, @@ -68,9 +76,11 @@ describe('notify', () => { icon: 'warning', title: 'test2', text: 'test alert fail 2', - timeout: AppNotificationTimeout.Warning, + showing: true, + timestamp, }, }, + lastRead: timestamp - 10, }; const result = appNotificationsReducer( @@ -81,7 +91,8 @@ describe('notify', () => { icon: 'info', title: 'test3', text: 'test alert info 3', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp: 1649802870373, }) ); @@ -93,7 +104,8 @@ describe('notify', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + timestamp, + showing: true, }, [id2]: { id: id2, @@ -101,7 +113,8 @@ describe('notify', () => { icon: 'warning', title: 'test2', text: 'test alert fail 2', - timeout: AppNotificationTimeout.Warning, + timestamp, + showing: true, }, [id3]: { id: id3, @@ -109,9 +122,11 @@ describe('notify', () => { icon: 'info', title: 'test3', text: 'test alert info 3', - timeout: AppNotificationTimeout.Success, + timestamp: 1649802870373, + showing: true, }, }, + lastRead: timestamp - 10, }; expect(result).toEqual(expectedResult); @@ -126,9 +141,11 @@ describe('notify', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp, }, }, + lastRead: timestamp - 10, }; const result = appNotificationsReducer( @@ -139,7 +156,8 @@ describe('notify', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp, }) ); @@ -151,9 +169,11 @@ describe('notify', () => { icon: 'success', title: 'test', text: 'test alert', - timeout: AppNotificationTimeout.Success, + showing: true, + timestamp, }, }, + lastRead: timestamp - 10, }; expect(result).toEqual(expectedResult); diff --git a/public/app/core/reducers/appNotification.ts b/public/app/core/reducers/appNotification.ts index 5da53d31c81..eff7f50c4a4 100644 --- a/public/app/core/reducers/appNotification.ts +++ b/public/app/core/reducers/appNotification.ts @@ -1,8 +1,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { AppNotification, AppNotificationsState } from 'app/types/'; + +import { config } from '@grafana/runtime'; +import { AppNotification, AppNotificationSeverity, AppNotificationsState } from 'app/types/'; + +export const STORAGE_KEY = 'notifications'; +export const NEW_NOTIFS_KEY = `${STORAGE_KEY}/lastRead`; +type StoredNotification = Omit; export const initialState: AppNotificationsState = { - byId: {}, + byId: deserializeNotifications(), + lastRead: Number.parseInt(window.localStorage.getItem(NEW_NOTIFS_KEY) ?? `${Date.now()}`, 10), }; /** @@ -16,30 +23,107 @@ const appNotificationsSlice = createSlice({ initialState, reducers: { notifyApp: (state, { payload: newAlert }: PayloadAction) => { - if (Object.values(state.byId).some((alert) => isSimilar(newAlert, alert))) { + if (Object.values(state.byId).some((alert) => isSimilar(newAlert, alert) && alert.showing)) { return; } state.byId[newAlert.id] = newAlert; + serializeNotifications(state.byId); }, - clearAppNotification: (state, { payload: alertId }: PayloadAction) => { + hideAppNotification: (state, { payload: alertId }: PayloadAction) => { + if (!(alertId in state.byId)) { + return; + } + + state.byId[alertId].showing = false; + serializeNotifications(state.byId); + }, + clearNotification: (state, { payload: alertId }: PayloadAction) => { delete state.byId[alertId]; + serializeNotifications(state.byId); + }, + clearAllNotifications: (state) => { + state.byId = {}; + serializeNotifications(state.byId); + }, + readAllNotifications: (state, { payload: timestamp }: PayloadAction) => { + state.lastRead = timestamp; }, }, }); -export const { notifyApp, clearAppNotification } = appNotificationsSlice.actions; +export const { notifyApp, hideAppNotification, clearNotification, clearAllNotifications, readAllNotifications } = + appNotificationsSlice.actions; export const appNotificationsReducer = appNotificationsSlice.reducer; -export const selectAll = (state: AppNotificationsState) => Object.values(state.byId); +// Selectors + +export const selectLastReadTimestamp = (state: AppNotificationsState) => state.lastRead; +export const selectAll = (state: AppNotificationsState) => + Object.values(state.byId).sort((a, b) => b.timestamp - a.timestamp); +export const selectWarningsAndErrors = (state: AppNotificationsState) => selectAll(state).filter(isAtLeastWarning); +export const selectVisible = (state: AppNotificationsState) => Object.values(state.byId).filter((n) => n.showing); + +// Helper functions function isSimilar(a: AppNotification, b: AppNotification): boolean { + return a.icon === b.icon && a.severity === b.severity && a.text === b.text && a.title === b.title; +} + +function isAtLeastWarning(notif: AppNotification) { + return notif.severity === AppNotificationSeverity.Warning || notif.severity === AppNotificationSeverity.Error; +} + +function isStoredNotification(obj: any): obj is StoredNotification { return ( - a.icon === b.icon && - a.severity === b.severity && - a.text === b.text && - a.title === b.title && - a.component === b.component + typeof obj.id === 'string' && + typeof obj.icon === 'string' && + typeof obj.title === 'string' && + typeof obj.text === 'string' ); } + +// (De)serialization + +export function deserializeNotifications(): Record { + if (!config.featureToggles?.persistNotifications) { + return {}; + } + + const storedNotifsRaw = window.localStorage.getItem(STORAGE_KEY); + if (!storedNotifsRaw) { + return {}; + } + + const parsed = JSON.parse(storedNotifsRaw); + if (!Object.values(parsed).every((v) => isStoredNotification(v))) { + return {}; + } + + return parsed; +} + +function serializeNotifications(notifs: Record) { + if (!config.featureToggles?.persistNotifications) { + return; + } + + const reducedNotifs = Object.values(notifs) + .filter(isAtLeastWarning) + .reduce>((prev, cur) => { + prev[cur.id] = { + id: cur.id, + severity: cur.severity, + icon: cur.icon, + title: cur.title, + text: cur.text, + traceId: cur.traceId, + timestamp: cur.timestamp, + showing: cur.showing, + }; + + return prev; + }, {}); + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(reducedNotifs)); +} diff --git a/public/app/core/reducers/index.ts b/public/app/core/reducers/index.ts index 8787fb2e440..6482f26c932 100644 --- a/public/app/core/reducers/index.ts +++ b/public/app/core/reducers/index.ts @@ -1,6 +1,6 @@ -import { navIndexReducer as navIndex } from './navModel'; -import { navTreeReducer as navBarTree } from './navBarTree'; import { appNotificationsReducer as appNotifications } from './appNotification'; +import { navTreeReducer as navBarTree } from './navBarTree'; +import { navIndexReducer as navIndex } from './navModel'; export default { navBarTree, diff --git a/public/app/core/reducers/navBarTree.ts b/public/app/core/reducers/navBarTree.ts index 61fbbd3dd13..3611e2501e4 100644 --- a/public/app/core/reducers/navBarTree.ts +++ b/public/app/core/reducers/navBarTree.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + import { NavModelItem } from '@grafana/data'; import config from 'app/core/config'; diff --git a/public/app/core/reducers/navModel.test.ts b/public/app/core/reducers/navModel.test.ts index b13874baa1f..38424eff8a5 100644 --- a/public/app/core/reducers/navModel.test.ts +++ b/public/app/core/reducers/navModel.test.ts @@ -1,7 +1,9 @@ -import { reducerTester } from '../../../test/core/redux/reducerTester'; -import { navIndexReducer, updateNavIndex, updateConfigurationSubtitle } from './navModel'; import { NavIndex } from '@grafana/data'; +import { reducerTester } from '../../../test/core/redux/reducerTester'; + +import { navIndexReducer, updateNavIndex, updateConfigurationSubtitle } from './navModel'; + describe('navModelReducer', () => { describe('when updateNavIndex is dispatched', () => { it('then state should be correct', () => { diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index 0e8ca795b84..bdb256bfe1a 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -1,6 +1,6 @@ import { AnyAction, createAction } from '@reduxjs/toolkit'; -import { NavIndex, NavModel, NavModelItem } from '@grafana/data'; +import { NavIndex, NavModel, NavModelItem } from '@grafana/data'; import config from 'app/core/config'; export function buildInitialState(): NavIndex { diff --git a/public/app/core/reducers/root.test.ts b/public/app/core/reducers/root.test.ts index 1f1de65851b..13b685c79f7 100644 --- a/public/app/core/reducers/root.test.ts +++ b/public/app/core/reducers/root.test.ts @@ -1,10 +1,11 @@ -import { createRootReducer, recursiveCleanState } from './root'; -import { describe, expect } from '../../../test/lib/common'; import { reducerTester } from '../../../test/core/redux/reducerTester'; -import { StoreState } from '../../types/store'; -import { Team } from '../../types'; -import { cleanUpAction } from '../actions/cleanUp'; +import { describe, expect } from '../../../test/lib/common'; import { initialTeamsState, teamsLoaded } from '../../features/teams/state/reducers'; +import { Team } from '../../types'; +import { StoreState } from '../../types/store'; +import { cleanUpAction } from '../actions/cleanUp'; + +import { createRootReducer, recursiveCleanState } from './root'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index fe0d22f773c..bab29a9acad 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -1,25 +1,27 @@ import { AnyAction, combineReducers } from 'redux'; -import { CleanUp, cleanUpAction } from '../actions/cleanUp'; + import sharedReducers from 'app/core/reducers'; -import alertingReducers from 'app/features/alerting/state/reducers'; -import teamsReducers from 'app/features/teams/state/reducers'; -import apiKeysReducers from 'app/features/api-keys/state/reducers'; -import foldersReducers from 'app/features/folders/state/reducers'; -import dashboardReducers from 'app/features/dashboard/state/reducers'; -import exploreReducers from 'app/features/explore/state/main'; -import { reducer as pluginsReducer } from 'app/features/plugins/admin/state/reducer'; -import dataSourcesReducers from 'app/features/datasources/state/reducers'; -import usersReducers from 'app/features/users/state/reducers'; -import invitesReducers from 'app/features/invites/state/reducers'; -import userReducers from 'app/features/profile/state/reducers'; -import organizationReducers from 'app/features/org/state/reducers'; import ldapReducers from 'app/features/admin/state/reducers'; -import importDashboardReducers from 'app/features/manage-dashboards/state/reducers'; +import alertingReducers from 'app/features/alerting/state/reducers'; +import apiKeysReducers from 'app/features/api-keys/state/reducers'; import panelEditorReducers from 'app/features/dashboard/components/PanelEditor/state/reducers'; +import dashboardReducers from 'app/features/dashboard/state/reducers'; +import dataSourcesReducers from 'app/features/datasources/state/reducers'; +import exploreReducers from 'app/features/explore/state/main'; +import foldersReducers from 'app/features/folders/state/reducers'; +import invitesReducers from 'app/features/invites/state/reducers'; +import importDashboardReducers from 'app/features/manage-dashboards/state/reducers'; +import organizationReducers from 'app/features/org/state/reducers'; import panelsReducers from 'app/features/panel/state/reducers'; +import { reducer as pluginsReducer } from 'app/features/plugins/admin/state/reducer'; +import userReducers from 'app/features/profile/state/reducers'; import serviceAccountsReducer from 'app/features/serviceaccounts/state/reducers'; +import teamsReducers from 'app/features/teams/state/reducers'; +import usersReducers from 'app/features/users/state/reducers'; import templatingReducers from 'app/features/variables/state/keyedVariablesReducer'; +import { CleanUp, cleanUpAction } from '../actions/cleanUp'; + const rootReducers = { ...sharedReducers, ...alertingReducers, diff --git a/public/app/core/services/FetchQueue.test.ts b/public/app/core/services/FetchQueue.test.ts index d0ab7b93548..79c7aa4fa77 100644 --- a/public/app/core/services/FetchQueue.test.ts +++ b/public/app/core/services/FetchQueue.test.ts @@ -1,5 +1,6 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; + import { BackendSrvRequest } from '@grafana/runtime'; import { FetchQueue, FetchQueueUpdate, FetchStatus } from './FetchQueue'; diff --git a/public/app/core/services/FetchQueueWorker.test.ts b/public/app/core/services/FetchQueueWorker.test.ts index 73682f1c93e..6f1d5dedb7b 100644 --- a/public/app/core/services/FetchQueueWorker.test.ts +++ b/public/app/core/services/FetchQueueWorker.test.ts @@ -1,11 +1,13 @@ import { Subject } from 'rxjs'; -import { FetchQueue, FetchQueueUpdate, FetchStatus } from './FetchQueue'; -import { ResponseQueue } from './ResponseQueue'; -import { FetchQueueWorker } from './FetchQueueWorker'; -import { expect } from '../../../test/lib/common'; import { GrafanaBootConfig } from '@grafana/runtime'; +import { expect } from '../../../test/lib/common'; + +import { FetchQueue, FetchQueueUpdate, FetchStatus } from './FetchQueue'; +import { FetchQueueWorker } from './FetchQueueWorker'; +import { ResponseQueue } from './ResponseQueue'; + const getTestContext = (http2Enabled = false) => { const config: GrafanaBootConfig = { http2Enabled } as unknown as GrafanaBootConfig; const dataUrl = 'http://localhost:3000/api/ds/query?=abc'; diff --git a/public/app/core/services/FetchQueueWorker.ts b/public/app/core/services/FetchQueueWorker.ts index 6ddaba7ed04..72c3c210943 100644 --- a/public/app/core/services/FetchQueueWorker.ts +++ b/public/app/core/services/FetchQueueWorker.ts @@ -1,8 +1,10 @@ import { concatMap, filter } from 'rxjs/operators'; -import { FetchQueue, FetchStatus } from './FetchQueue'; import { BackendSrvRequest, GrafanaBootConfig } from '@grafana/runtime'; + import { isDataQuery } from '../utils/query'; + +import { FetchQueue, FetchStatus } from './FetchQueue'; import { ResponseQueue } from './ResponseQueue'; interface WorkerEntry { diff --git a/public/app/core/services/ModalManager.ts b/public/app/core/services/ModalManager.ts index 395378c4e85..cdd756f8f36 100644 --- a/public/app/core/services/ModalManager.ts +++ b/public/app/core/services/ModalManager.ts @@ -1,15 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import appEvents from 'app/core/app_events'; -import { AngularModalProxy } from '../components/modals/AngularModalProxy'; -import { provideTheme } from '../utils/ConfigProvider'; -import { ShowConfirmModalEvent, ShowConfirmModalPayload, ShowModalReactEvent } from '../../types/events'; -import { ConfirmModal, ConfirmModalProps } from '@grafana/ui'; import { textUtil } from '@grafana/data'; import { CopyPanelEvent } from '@grafana/runtime'; +import { ConfirmModal, ConfirmModalProps } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; import { copyPanel } from 'app/features/dashboard/utils/panel'; +import { ShowConfirmModalEvent, ShowConfirmModalPayload, ShowModalReactEvent } from '../../types/events'; +import { AngularModalProxy } from '../components/modals/AngularModalProxy'; +import { provideTheme } from '../utils/ConfigProvider'; + export class ModalManager { reactModalRoot = document.body; reactModalNode = document.createElement('div'); diff --git a/public/app/core/services/PreferencesService.ts b/public/app/core/services/PreferencesService.ts index 174e3dd7d5b..d2ab11e23cd 100644 --- a/public/app/core/services/PreferencesService.ts +++ b/public/app/core/services/PreferencesService.ts @@ -1,4 +1,5 @@ import { UserPreferencesDTO } from 'app/types'; + import { backendSrv } from './backend_srv'; export class PreferencesService { diff --git a/public/app/core/services/ResponseQueue.test.ts b/public/app/core/services/ResponseQueue.test.ts index 29e9fd580de..5c8c19c4675 100644 --- a/public/app/core/services/ResponseQueue.test.ts +++ b/public/app/core/services/ResponseQueue.test.ts @@ -1,12 +1,14 @@ import { of } from 'rxjs'; import { first } from 'rxjs/operators'; + import { BackendSrvRequest } from '@grafana/runtime'; -import { FetchQueue, FetchQueueUpdate } from './FetchQueue'; -import { ResponseQueue } from './ResponseQueue'; -import { subscribeTester } from './FetchQueue.test'; import { describe, expect } from '../../../test/lib/common'; +import { FetchQueue, FetchQueueUpdate } from './FetchQueue'; +import { subscribeTester } from './FetchQueue.test'; +import { ResponseQueue } from './ResponseQueue'; + const getTestContext = () => { const id = 'id'; const options: BackendSrvRequest = { url: 'http://someurl' }; diff --git a/public/app/core/services/ResponseQueue.ts b/public/app/core/services/ResponseQueue.ts index 9f5c7769641..567263bd8bd 100644 --- a/public/app/core/services/ResponseQueue.ts +++ b/public/app/core/services/ResponseQueue.ts @@ -1,6 +1,8 @@ import { Observable, Subject } from 'rxjs'; import { filter } from 'rxjs/operators'; + import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; + import { FetchQueue } from './FetchQueue'; interface FetchWorkEntry { diff --git a/public/app/core/services/__mocks__/backend_srv.ts b/public/app/core/services/__mocks__/backend_srv.ts index 2c79192a372..f43e5b77d4d 100644 --- a/public/app/core/services/__mocks__/backend_srv.ts +++ b/public/app/core/services/__mocks__/backend_srv.ts @@ -1,7 +1,9 @@ import 'whatwg-fetch'; // fetch polyfill needed for Headers -import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; import { of } from 'rxjs'; + +import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; + import { BackendSrv } from '../backend_srv'; /** diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 72e7c92b993..74855d0600a 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -9,17 +9,19 @@ import { Subscription, throwError, } from 'rxjs'; -import { catchError, filter, map, mergeMap, retryWhen, share, takeUntil, tap, throwIfEmpty } from 'rxjs/operators'; import { fromFetch } from 'rxjs/fetch'; +import { catchError, filter, map, mergeMap, retryWhen, share, takeUntil, tap, throwIfEmpty } from 'rxjs/operators'; import { v4 as uuidv4 } from 'uuid'; -import { BackendSrv as BackendService, BackendSrvRequest, FetchError, FetchResponse } from '@grafana/runtime'; -import { AppEvents, DataQueryErrorType } from '@grafana/data'; +import { AppEvents, DataQueryErrorType } from '@grafana/data'; +import { BackendSrv as BackendService, BackendSrvRequest, FetchError, FetchResponse } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import { getConfig } from 'app/core/config'; import { DashboardSearchHit } from 'app/features/search/types'; +import { TokenRevokedModal } from 'app/features/users/TokenRevokedModal'; import { DashboardDTO, FolderDTO } from 'app/types'; -import { ContextSrv, contextSrv } from './context_srv'; + +import { ShowModalReactEvent } from '../../types/events'; import { isContentTypeApplicationJson, parseInitFromOptions, @@ -27,11 +29,11 @@ import { parseUrlFromOptions, } from '../utils/fetch'; import { isDataQuery, isLocalUrl } from '../utils/query'; + import { FetchQueue } from './FetchQueue'; -import { ResponseQueue } from './ResponseQueue'; import { FetchQueueWorker } from './FetchQueueWorker'; -import { TokenRevokedModal } from 'app/features/users/TokenRevokedModal'; -import { ShowModalReactEvent } from '../../types/events'; +import { ResponseQueue } from './ResponseQueue'; +import { ContextSrv, contextSrv } from './context_srv'; const CANCEL_ALL_REQUESTS_REQUEST_ID = 'cancel_all_requests_request_id'; @@ -310,6 +312,7 @@ export class BackendSrv implements BackendService { this.dependencies.appEvents.emit(err.status < 500 ? AppEvents.alertWarning : AppEvents.alertError, [ message, description, + err.data.traceID, ]); } diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index ecee561f4a0..6930600ad3d 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -1,10 +1,12 @@ -import config from '../../core/config'; import { extend } from 'lodash'; + import { OrgRole, rangeUtil, WithAccessControlMetadata } from '@grafana/data'; -import { AccessControlAction, UserPermission } from 'app/types'; import { featureEnabled, getBackendSrv } from '@grafana/runtime'; +import { AccessControlAction, UserPermission } from 'app/types'; import { CurrentUserInternal } from 'app/types/config'; +import config from '../../core/config'; + export class User implements CurrentUserInternal { isSignedIn: boolean; id: number; diff --git a/public/app/core/services/echo/Echo.ts b/public/app/core/services/echo/Echo.ts index 92d24d470f9..755576922e2 100644 --- a/public/app/core/services/echo/Echo.ts +++ b/public/app/core/services/echo/Echo.ts @@ -1,5 +1,7 @@ import { EchoBackend, EchoMeta, EchoEvent, EchoSrv } from '@grafana/runtime'; + import { contextSrv } from '../context_srv'; + import { echoLog } from './utils'; interface EchoConfig { diff --git a/public/app/core/services/echo/EchoSrv.ts b/public/app/core/services/echo/EchoSrv.ts index 1f230eca4a5..1f91e09fd79 100644 --- a/public/app/core/services/echo/EchoSrv.ts +++ b/public/app/core/services/echo/EchoSrv.ts @@ -1,5 +1,7 @@ -import { getEchoSrv, EchoEventType } from '@grafana/runtime'; import { captureException } from '@sentry/browser'; + +import { getEchoSrv, EchoEventType } from '@grafana/runtime'; + import { PerformanceEvent } from './backends/PerformanceBackend'; export const reportPerformance = (metric: string, value: number) => { diff --git a/public/app/core/services/echo/backends/PerformanceBackend.ts b/public/app/core/services/echo/backends/PerformanceBackend.ts index 5cc0385ab66..5b3d9af2830 100644 --- a/public/app/core/services/echo/backends/PerformanceBackend.ts +++ b/public/app/core/services/echo/backends/PerformanceBackend.ts @@ -1,4 +1,5 @@ import { EchoBackend, EchoEvent, EchoEventType } from '@grafana/runtime'; + import { backendSrv } from '../../backend_srv'; export interface PerformanceEventPayload { diff --git a/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts b/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts index 96dacbc9081..caaef40fe51 100644 --- a/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts +++ b/public/app/core/services/echo/backends/analytics/ApplicationInsightsBackend.ts @@ -1,4 +1,5 @@ import $ from 'jquery'; + import { EchoBackend, EchoEventType, diff --git a/public/app/core/services/echo/backends/analytics/GABackend.ts b/public/app/core/services/echo/backends/analytics/GABackend.ts index 6a6401f73e3..dd65adf32d9 100644 --- a/public/app/core/services/echo/backends/analytics/GABackend.ts +++ b/public/app/core/services/echo/backends/analytics/GABackend.ts @@ -1,4 +1,5 @@ import $ from 'jquery'; + import { EchoBackend, EchoEventType, PageviewEchoEvent } from '@grafana/runtime'; export interface GAEchoBackendOptions { diff --git a/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts b/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts index 0f3b16c4923..f18362393d8 100644 --- a/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts +++ b/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts @@ -1,4 +1,6 @@ import $ from 'jquery'; + +import { CurrentUserDTO } from '@grafana/data'; import { EchoBackend, EchoEventType, @@ -7,7 +9,7 @@ import { isPageviewEvent, PageviewEchoEvent, } from '@grafana/runtime'; -import { CurrentUserDTO } from '@grafana/data'; + import { getUserIdentifier } from '../../utils'; export interface RudderstackBackendOptions { diff --git a/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts b/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts index a11380a68a1..62b1cd7c5ae 100644 --- a/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts +++ b/public/app/core/services/echo/backends/sentry/SentryBackend.test.ts @@ -1,14 +1,17 @@ import { init as initSentry, setUser as sentrySetUser, Event as SentryEvent } from '@sentry/browser'; -import { SentryEchoBackend, SentryEchoBackendOptions } from './SentryBackend'; -import { BuildInfo } from '@grafana/data'; import { FetchTransport } from '@sentry/browser/dist/transports'; +import { waitFor } from '@testing-library/react'; + +import { BuildInfo } from '@grafana/data'; +import { GrafanaEdition } from '@grafana/data/src/types/config'; +import { EchoBackend, EchoEventType, EchoMeta, setEchoSrv } from '@grafana/runtime'; + +import { Echo } from '../../Echo'; + +import { SentryEchoBackend, SentryEchoBackendOptions } from './SentryBackend'; import { CustomEndpointTransport } from './transports/CustomEndpointTransport'; import { EchoSrvTransport } from './transports/EchoSrvTransport'; import { SentryEchoEvent } from './types'; -import { EchoBackend, EchoEventType, EchoMeta, setEchoSrv } from '@grafana/runtime'; -import { waitFor } from '@testing-library/react'; -import { Echo } from '../../Echo'; -import { GrafanaEdition } from '@grafana/data/src/types/config'; jest.mock('@sentry/browser'); diff --git a/public/app/core/services/echo/backends/sentry/SentryBackend.ts b/public/app/core/services/echo/backends/sentry/SentryBackend.ts index 00753252a56..08211be9d82 100644 --- a/public/app/core/services/echo/backends/sentry/SentryBackend.ts +++ b/public/app/core/services/echo/backends/sentry/SentryBackend.ts @@ -1,10 +1,12 @@ -import { EchoBackend, EchoEventType } from '@grafana/runtime'; -import { SentryConfig } from '@grafana/data/src/types/config'; import { BrowserOptions, init as initSentry, setUser as sentrySetUser } from '@sentry/browser'; import { FetchTransport } from '@sentry/browser/dist/transports'; + +import { BuildInfo } from '@grafana/data'; +import { SentryConfig } from '@grafana/data/src/types/config'; +import { EchoBackend, EchoEventType } from '@grafana/runtime'; + import { CustomEndpointTransport } from './transports/CustomEndpointTransport'; import { EchoSrvTransport } from './transports/EchoSrvTransport'; -import { BuildInfo } from '@grafana/data'; import { SentryEchoEvent, User, BaseTransport } from './types'; export interface SentryEchoBackendOptions extends SentryConfig { diff --git a/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts index 16f6e484f4e..c38ff48c919 100644 --- a/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts +++ b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.test.ts @@ -1,4 +1,5 @@ import { Event, Severity } from '@sentry/browser'; + import { CustomEndpointTransport } from './CustomEndpointTransport'; describe('CustomEndpointTransport', () => { diff --git a/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts index afe300c6ad4..cf828758a8b 100644 --- a/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts +++ b/public/app/core/services/echo/backends/sentry/transports/CustomEndpointTransport.ts @@ -1,4 +1,5 @@ import { Event, Severity } from '@sentry/browser'; +import { Response } from '@sentry/types'; import { logger, makePromiseBuffer, @@ -7,7 +8,7 @@ import { supportsReferrerPolicy, SyncPromise, } from '@sentry/utils'; -import { Response } from '@sentry/types'; + import { BaseTransport } from '../types'; export interface CustomEndpointTransportOptions { diff --git a/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts b/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts index b3555b17568..4d3db59eca7 100644 --- a/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts +++ b/public/app/core/services/echo/backends/sentry/transports/EchoSrvTransport.ts @@ -1,8 +1,9 @@ -import { getEchoSrv, EchoEventType } from '@grafana/runtime'; -import { BaseTransport } from '@sentry/browser/dist/transports'; import { Event } from '@sentry/browser'; +import { BaseTransport } from '@sentry/browser/dist/transports'; import { EventStatus, Request, Session, Response } from '@sentry/types'; +import { getEchoSrv, EchoEventType } from '@grafana/runtime'; + export class EchoSrvTransport extends BaseTransport { sendEvent(event: Event) { getEchoSrv().addEvent({ diff --git a/public/app/core/services/echo/backends/sentry/types.ts b/public/app/core/services/echo/backends/sentry/types.ts index 9d5906a2d53..a19fe3c0856 100644 --- a/public/app/core/services/echo/backends/sentry/types.ts +++ b/public/app/core/services/echo/backends/sentry/types.ts @@ -1,7 +1,8 @@ -import { EchoEvent, EchoEventType } from '@grafana/runtime'; import { Event as SentryEvent } from '@sentry/browser'; import { Response } from '@sentry/types'; +import { EchoEvent, EchoEventType } from '@grafana/runtime'; + export interface BaseTransport { sendEvent(event: SentryEvent): PromiseLike; } diff --git a/public/app/core/services/echo/utils.test.ts b/public/app/core/services/echo/utils.test.ts index 0b646cd8363..06406970ab5 100644 --- a/public/app/core/services/echo/utils.test.ts +++ b/public/app/core/services/echo/utils.test.ts @@ -1,4 +1,5 @@ import { CurrentUserDTO, OrgRole } from '@grafana/data'; + import { getUserIdentifier } from './utils'; const baseUser: CurrentUserDTO = { diff --git a/public/app/core/services/echo/utils.ts b/public/app/core/services/echo/utils.ts index edcfe096d77..a8a75d92953 100644 --- a/public/app/core/services/echo/utils.ts +++ b/public/app/core/services/echo/utils.ts @@ -1,6 +1,5 @@ -import { attachDebugger, createLogger } from '@grafana/ui'; - import { CurrentUserDTO } from '@grafana/data'; +import { attachDebugger, createLogger } from '@grafana/ui'; /** * Returns an opaque identifier for a user, for reporting purposes. diff --git a/public/app/core/services/impression_srv.ts b/public/app/core/services/impression_srv.ts index 95c1acf36f3..f26b85dc542 100644 --- a/public/app/core/services/impression_srv.ts +++ b/public/app/core/services/impression_srv.ts @@ -1,6 +1,7 @@ -import store from 'app/core/store'; import { filter, isArray, isNumber } from 'lodash'; + import config from 'app/core/config'; +import store from 'app/core/store'; export class ImpressionSrv { constructor() {} diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 598f8448fc6..0e9755af3a8 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -1,14 +1,17 @@ import Mousetrap from 'mousetrap'; + import 'mousetrap-global-bind'; import 'mousetrap/plugins/global-bind/mousetrap-global-bind'; import { LegacyGraphHoverClearEvent, locationUtil } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import { getExploreUrl } from 'app/core/utils/explore'; -import { DashboardModel } from 'app/features/dashboard/state'; -import { ShareModal } from 'app/features/dashboard/components/ShareModal'; import { SaveDashboardProxy } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardProxy'; -import { locationService } from '@grafana/runtime'; -import { exitKioskMode, toggleKioskMode } from '../navigation/kiosk'; +import { ShareModal } from 'app/features/dashboard/components/ShareModal'; +import { DashboardModel } from 'app/features/dashboard/state'; + +import { getTimeSrv } from '../../features/dashboard/services/TimeSrv'; +import { getDatasourceSrv } from '../../features/plugins/datasource_srv'; import { RemovePanelEvent, ShiftTimeEvent, @@ -17,12 +20,12 @@ import { ZoomOutEvent, AbsoluteTimeEvent, } from '../../types/events'; +import { HelpModal } from '../components/help/HelpModal'; import { contextSrv } from '../core'; -import { getDatasourceSrv } from '../../features/plugins/datasource_srv'; -import { getTimeSrv } from '../../features/dashboard/services/TimeSrv'; +import { exitKioskMode, toggleKioskMode } from '../navigation/kiosk'; + import { toggleTheme } from './toggleTheme'; import { withFocusedPanel } from './withFocusedPanelId'; -import { HelpModal } from '../components/help/HelpModal'; export class KeybindingSrv { reset() { diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index 581a90f662c..a5030a3c22a 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -1,10 +1,12 @@ import { clone, keys, sortBy, take, values } from 'lodash'; + +import { contextSrv } from 'app/core/services/context_srv'; import impressionSrv from 'app/core/services/impression_srv'; import store from 'app/core/store'; -import { contextSrv } from 'app/core/services/context_srv'; -import { hasFilters } from 'app/features/search/utils'; import { SECTION_STORAGE_KEY } from 'app/features/search/constants'; import { DashboardSection, DashboardSearchItemType, DashboardSearchHit, SearchLayout } from 'app/features/search/types'; +import { hasFilters } from 'app/features/search/utils'; + import { backendSrv } from './backend_srv'; interface Sections { diff --git a/public/app/core/services/toggleTheme.ts b/public/app/core/services/toggleTheme.ts index 2cb4d1b7770..6aeca20546a 100644 --- a/public/app/core/services/toggleTheme.ts +++ b/public/app/core/services/toggleTheme.ts @@ -1,9 +1,11 @@ +import { createTheme } from '@grafana/data'; import { ThemeChangedEvent } from '@grafana/runtime'; + import appEvents from '../app_events'; import { config } from '../config'; -import { PreferencesService } from './PreferencesService'; import { contextSrv } from '../core'; -import { createTheme } from '@grafana/data'; + +import { PreferencesService } from './PreferencesService'; export async function toggleTheme(runtimeOnly: boolean) { const currentTheme = config.theme; diff --git a/public/app/core/specs/OrgSwitcher.test.tsx b/public/app/core/specs/OrgSwitcher.test.tsx index 829fa3ac9c0..cb612259ec3 100644 --- a/public/app/core/specs/OrgSwitcher.test.tsx +++ b/public/app/core/specs/OrgSwitcher.test.tsx @@ -1,10 +1,10 @@ -import React from 'react'; import { render, screen, waitFor, within } from '@testing-library/react'; - -import { OrgSwitcher } from '../components/OrgSwitcher'; -import { api } from '../../features/profile/api'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { api } from '../../features/profile/api'; import { OrgRole } from '../../types'; +import { OrgSwitcher } from '../components/OrgSwitcher'; jest.mock('@grafana/runtime', () => ({ config: { @@ -53,7 +53,7 @@ describe('OrgSwitcher', () => { it('should switch orgId in call to backend', async () => { const row = screen.getByRole('row', { name: /org 2 admin switch to/i }); const switchToButton = within(row).getByText(/switch to/i); - userEvent.click(switchToButton); + await userEvent.click(switchToButton); await waitFor(() => expect(setUserOrgSpy).toBeCalledWith({ orgId: 2, name: 'Org 2', role: 'Admin' })); }); @@ -63,7 +63,7 @@ describe('OrgSwitcher', () => { const row = screen.getByRole('row', { name: /org 2 admin switch to/i }); const switchToButton = within(row).getByText(/switch to/i); - userEvent.click(switchToButton); + await userEvent.click(switchToButton); await waitFor(() => expect(window.location.href).toEqual('/subUrl/?orgId=2')); }); diff --git a/public/app/core/specs/backend_srv.test.ts b/public/app/core/specs/backend_srv.test.ts index c66a4acd70e..e9c5c095101 100644 --- a/public/app/core/specs/backend_srv.test.ts +++ b/public/app/core/specs/backend_srv.test.ts @@ -1,13 +1,14 @@ import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering import { Observable, of } from 'rxjs'; import { delay } from 'rxjs/operators'; -import { AppEvents, DataQueryErrorType, EventBusExtended } from '@grafana/data'; -import { BackendSrv } from '../services/backend_srv'; -import { ContextSrv, User } from '../services/context_srv'; +import { AppEvents, DataQueryErrorType, EventBusExtended } from '@grafana/data'; import { BackendSrvRequest, FetchError } from '@grafana/runtime'; + import { TokenRevokedModal } from '../../features/users/TokenRevokedModal'; import { ShowModalReactEvent } from '../../types/events'; +import { BackendSrv } from '../services/backend_srv'; +import { ContextSrv, User } from '../services/context_srv'; const getTestContext = (overides?: object) => { const defaults = { @@ -264,10 +265,15 @@ describe('backendSrv', () => { data: { message: 'Something failed', error: 'Error', + traceID: 'bogus-trace-id', }, } as FetchError ); - expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertError, ['Something failed', '']); + expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertError, [ + 'Something failed', + '', + 'bogus-trace-id', + ]); }); }); }); diff --git a/public/app/core/specs/search_srv.test.ts b/public/app/core/specs/search_srv.test.ts index 1a03c1f96d2..5fe30ef2df1 100644 --- a/public/app/core/specs/search_srv.test.ts +++ b/public/app/core/specs/search_srv.test.ts @@ -1,7 +1,9 @@ -import { SearchSrv } from 'app/core/services/search_srv'; -import impressionSrv from 'app/core/services/impression_srv'; -import { contextSrv } from 'app/core/services/context_srv'; import { beforeEach } from 'test/lib/common'; + +import { contextSrv } from 'app/core/services/context_srv'; +import impressionSrv from 'app/core/services/impression_srv'; +import { SearchSrv } from 'app/core/services/search_srv'; + import { backendSrv } from '../services/backend_srv'; jest.mock('app/core/store', () => { diff --git a/public/app/core/table_model.ts b/public/app/core/table_model.ts index f2c83e40aa2..901345ce562 100644 --- a/public/app/core/table_model.ts +++ b/public/app/core/table_model.ts @@ -1,4 +1,5 @@ import { findIndex } from 'lodash'; + import { Column, TableData, QueryResultMeta } from '@grafana/data'; /** diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index 8a9a515d489..e2c301d50a9 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -1,4 +1,5 @@ import { isNumber, isFinite, escape } from 'lodash'; + import { DecimalCount, formattedValueToString, getValueFormat, stringToJsRegex, ValueFormatter } from '@grafana/data'; function matchSeriesOverride(aliasOrRegex: string, seriesAlias: string) { diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index fe9e64d28bb..e714ab27b09 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -1,8 +1,10 @@ import React, { useEffect, useState } from 'react'; + +import { createTheme } from '@grafana/data'; import { config, GrafanaBootConfig, ThemeChangedEvent } from '@grafana/runtime'; import { ThemeContext } from '@grafana/ui'; + import { appEvents } from '../core'; -import { createTheme } from '@grafana/data'; export const ConfigContext = React.createContext(config); export const ConfigConsumer = ConfigContext.Consumer; diff --git a/public/app/core/utils/connectWithReduxStore.tsx b/public/app/core/utils/connectWithReduxStore.tsx index c7dc4591b9b..83f3b519fd3 100644 --- a/public/app/core/utils/connectWithReduxStore.tsx +++ b/public/app/core/utils/connectWithReduxStore.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { connect, Provider } from 'react-redux'; + import { store } from '../../store/store'; export function connectWithStore(WrappedComponent: any, ...args: any[]) { diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 7b1a4d2b999..49bccf92632 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -1,3 +1,10 @@ +import { dateTime, ExploreUrlState, LogsSortOrder } from '@grafana/data'; +import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; +import { RefreshPicker } from '@grafana/ui'; +import store from 'app/core/store'; + +import { ExploreId } from '../../types'; + import { buildQueryTransaction, clearHistory, @@ -13,11 +20,6 @@ import { getTimeRangeFromUrl, getTimeRange, } from './explore'; -import store from 'app/core/store'; -import { dateTime, ExploreUrlState, LogsSortOrder } from '@grafana/data'; -import { RefreshPicker } from '@grafana/ui'; -import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; -import { ExploreId } from '../../types'; const DEFAULT_EXPLORE_STATE: ExploreUrlState = { datasource: '', diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index d4181c25086..a28a58909bb 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -1,7 +1,7 @@ -// Libraries import { flatten, omit, uniq } from 'lodash'; import { Unsubscribable } from 'rxjs'; -// Services & Utils +import { v4 as uuidv4 } from 'uuid'; + import { CoreApp, DataQuery, @@ -24,16 +24,16 @@ import { toUtc, urlUtil, } from '@grafana/data'; -import store from 'app/core/store'; -import { v4 as uuidv4 } from 'uuid'; -import { getNextRefIdChar } from './query'; -// Types -import { RefreshPicker } from '@grafana/ui'; -import { EXPLORE_GRAPH_STYLES, ExploreGraphStyle, ExploreId, QueryOptions, QueryTransaction } from 'app/types/explore'; -import { config } from '../config'; -import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DataSourceSrv } from '@grafana/runtime'; +import { RefreshPicker } from '@grafana/ui'; +import store from 'app/core/store'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { PanelModel } from 'app/features/dashboard/state'; +import { EXPLORE_GRAPH_STYLES, ExploreGraphStyle, ExploreId, QueryOptions, QueryTransaction } from 'app/types/explore'; + +import { config } from '../config'; + +import { getNextRefIdChar } from './query'; export const DEFAULT_RANGE = { from: 'now-1h', diff --git a/public/app/core/utils/fetch.ts b/public/app/core/utils/fetch.ts index fb77a6d1a7d..c4b00d2034e 100644 --- a/public/app/core/utils/fetch.ts +++ b/public/app/core/utils/fetch.ts @@ -1,6 +1,7 @@ -import { BackendSrvRequest } from '@grafana/runtime'; import { omitBy } from 'lodash'; + import { deprecationWarning } from '@grafana/data'; +import { BackendSrvRequest } from '@grafana/runtime'; export const parseInitFromOptions = (options: BackendSrvRequest): RequestInit => { const method = options.method; diff --git a/public/app/core/utils/kbn.test.ts b/public/app/core/utils/kbn.test.ts index 1ebae32ecc1..6e291f3d1b2 100644 --- a/public/app/core/utils/kbn.test.ts +++ b/public/app/core/utils/kbn.test.ts @@ -1,6 +1,7 @@ -import kbn from './kbn'; import { DecimalCount, TimeZone } from '@grafana/data'; +import kbn from './kbn'; + // Most of the methods in this file are deprecated // Stub the deprecation warning here to prevent polluting the test output jest.mock('@grafana/data', () => ({ diff --git a/public/app/core/utils/query.test.ts b/public/app/core/utils/query.test.ts index 00c69667432..4b2028ce544 100644 --- a/public/app/core/utils/query.test.ts +++ b/public/app/core/utils/query.test.ts @@ -1,4 +1,5 @@ import { DataQuery } from '@grafana/data'; + import { getNextRefIdChar } from './query'; function dataQueryHelper(ids: string[]): DataQuery[] { diff --git a/public/app/core/utils/richHistory.test.ts b/public/app/core/utils/richHistory.test.ts index cd6b12a58ac..446362f9a72 100644 --- a/public/app/core/utils/richHistory.test.ts +++ b/public/app/core/utils/richHistory.test.ts @@ -1,3 +1,9 @@ +import { dateTime, DataQuery } from '@grafana/data'; +import store from 'app/core/store'; + +import { RichHistoryQuery } from '../../types'; +import RichHistoryStorage, { RichHistoryStorageWarning } from '../history/RichHistoryStorage'; + import { addToRichHistory, updateStarredInRichHistory, @@ -10,10 +16,6 @@ import { filterAndSortQueries, SortOrder, } from './richHistory'; -import store from 'app/core/store'; -import { dateTime, DataQuery } from '@grafana/data'; -import RichHistoryStorage, { RichHistoryStorageWarning } from '../history/RichHistoryStorage'; -import { RichHistoryQuery } from '../../types'; const richHistoryStorageMock: RichHistoryStorage = {} as RichHistoryStorage; diff --git a/public/app/core/utils/richHistory.ts b/public/app/core/utils/richHistory.ts index 5e08a4fa046..2cb8efe0365 100644 --- a/public/app/core/utils/richHistory.ts +++ b/public/app/core/utils/richHistory.ts @@ -1,28 +1,26 @@ -// Libraries import { omit } from 'lodash'; -// Services & Utils import { DataQuery, DataSourceApi, dateTimeFormat, ExploreUrlState, urlUtil } from '@grafana/data'; -import { dispatch } from 'app/store/store'; -import { notifyApp } from 'app/core/actions'; -import { createErrorNotification, createWarningNotification } from 'app/core/copy/appNotification'; - -// Types -import { RichHistoryQuery } from 'app/types/explore'; import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; import { getDataSourceSrv } from '@grafana/runtime'; -import { getRichHistoryStorage } from '../history/richHistoryStorageProvider'; -import { - RichHistoryServiceError, - RichHistoryStorageWarning, - RichHistoryStorageWarningDetails, -} from '../history/RichHistoryStorage'; +import { notifyApp } from 'app/core/actions'; +import { createErrorNotification, createWarningNotification } from 'app/core/copy/appNotification'; import { filterQueriesByDataSource, filterQueriesBySearchFilter, filterQueriesByTime, sortQueries, } from 'app/core/history/richHistoryLocalStorageUtils'; +import { dispatch } from 'app/store/store'; +import { RichHistoryQuery } from 'app/types/explore'; + +import { + RichHistoryServiceError, + RichHistoryStorageWarning, + RichHistoryStorageWarningDetails, +} from '../history/RichHistoryStorage'; +import { getRichHistoryStorage } from '../history/richHistoryStorageProvider'; + import { RichHistorySettings, SortOrder } from './richHistoryTypes'; export { SortOrder }; diff --git a/public/app/core/utils/shortLinks.ts b/public/app/core/utils/shortLinks.ts index 07430542dc8..30b6beb1c24 100644 --- a/public/app/core/utils/shortLinks.ts +++ b/public/app/core/utils/shortLinks.ts @@ -1,9 +1,11 @@ import memoizeOne from 'memoize-one'; + import { getBackendSrv, config } from '@grafana/runtime'; -import { copyStringToClipboard } from './explore'; -import { dispatch } from 'app/store/store'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; +import { dispatch } from 'app/store/store'; + +import { copyStringToClipboard } from './explore'; function buildHostUrl() { return `${window.location.protocol}//${window.location.host}${config.appSubUrl}`; diff --git a/public/app/features/admin/AdminEditOrgPage.tsx b/public/app/features/admin/AdminEditOrgPage.tsx index bad9182189c..9afb7c4484c 100644 --- a/public/app/features/admin/AdminEditOrgPage.tsx +++ b/public/app/features/admin/AdminEditOrgPage.tsx @@ -1,17 +1,19 @@ -import React, { useState, useEffect } from 'react'; -import Page from 'app/core/components/Page/Page'; -import { useSelector } from 'react-redux'; -import { StoreState, OrgUser, AccessControlAction } from 'app/types'; -import { getNavModel } from 'app/core/selectors/navModel'; -import UsersTable from '../users/UsersTable'; -import { useAsyncFn } from 'react-use'; -import { getBackendSrv } from '@grafana/runtime'; -import { UrlQueryValue } from '@grafana/data'; -import { Form, Field, Input, Button, Legend, Alert } from '@grafana/ui'; import { css } from '@emotion/css'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import React, { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useAsyncFn } from 'react-use'; + +import { UrlQueryValue } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; +import { Form, Field, Input, Button, Legend, Alert } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; import { accessControlQueryParam } from 'app/core/utils/accessControl'; +import { StoreState, OrgUser, AccessControlAction } from 'app/types'; + +import UsersTable from '../users/UsersTable'; interface OrgNameDTO { orgName: string; diff --git a/public/app/features/admin/AdminListOrgsPage.tsx b/public/app/features/admin/AdminListOrgsPage.tsx index f9e70493822..6fff515aa09 100644 --- a/public/app/features/admin/AdminListOrgsPage.tsx +++ b/public/app/features/admin/AdminListOrgsPage.tsx @@ -1,14 +1,16 @@ import React, { useEffect } from 'react'; -import { getNavModel } from 'app/core/selectors/navModel'; -import Page from 'app/core/components/Page/Page'; import { useSelector } from 'react-redux'; -import { StoreState } from 'app/types/store'; -import { LinkButton } from '@grafana/ui'; -import { getBackendSrv } from '@grafana/runtime'; -import { AdminOrgsTable } from './AdminOrgsTable'; import useAsyncFn from 'react-use/lib/useAsyncFn'; + +import { getBackendSrv } from '@grafana/runtime'; +import { LinkButton } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; +import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; +import { StoreState } from 'app/types/store'; + +import { AdminOrgsTable } from './AdminOrgsTable'; const deleteOrg = async (orgId: number) => { return await getBackendSrv().delete('/api/orgs/' + orgId); diff --git a/public/app/features/admin/AdminOrgsTable.tsx b/public/app/features/admin/AdminOrgsTable.tsx index f8c9003d80d..6a042ca6af2 100644 --- a/public/app/features/admin/AdminOrgsTable.tsx +++ b/public/app/features/admin/AdminOrgsTable.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; -import { AccessControlAction, Organization } from 'app/types'; + import { Button, ConfirmModal } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction, Organization } from 'app/types'; interface Props { orgs: Organization[]; diff --git a/public/app/features/admin/AdminSettings.tsx b/public/app/features/admin/AdminSettings.tsx index d54470c49cc..e2e2a0c7ecb 100644 --- a/public/app/features/admin/AdminSettings.tsx +++ b/public/app/features/admin/AdminSettings.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; import { useAsync } from 'react-use'; -import { getBackendSrv } from '@grafana/runtime'; -import { NavModel } from '@grafana/data'; -import { StoreState } from 'app/types'; -import { getNavModel } from 'app/core/selectors/navModel'; +import { NavModel } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; import Page from 'app/core/components/Page/Page'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; type Settings = { [key: string]: { [key: string]: string } }; diff --git a/public/app/features/admin/CrawlerStartButton.tsx b/public/app/features/admin/CrawlerStartButton.tsx index 1035eac199e..3cceb47b7f3 100644 --- a/public/app/features/admin/CrawlerStartButton.tsx +++ b/public/app/features/admin/CrawlerStartButton.tsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; -import { Button, CodeEditor, Modal, useTheme2 } from '@grafana/ui'; +import React, { useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { getBackendSrv, config } from '@grafana/runtime'; +import { Button, CodeEditor, Modal, useTheme2 } from '@grafana/ui'; export const CrawlerStartButton = () => { const styles = getStyles(useTheme2()); diff --git a/public/app/features/admin/CrawlerStatus.tsx b/public/app/features/admin/CrawlerStatus.tsx index 50dd9f97cdf..5880f43ec39 100644 --- a/public/app/features/admin/CrawlerStatus.tsx +++ b/public/app/features/admin/CrawlerStatus.tsx @@ -1,8 +1,10 @@ -import React, { useEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { Button, useTheme2 } from '@grafana/ui'; +import React, { useEffect, useState } from 'react'; + import { GrafanaTheme2, isLiveChannelMessageEvent, isLiveChannelStatusEvent, LiveChannelScope } from '@grafana/data'; import { getBackendSrv, getGrafanaLiveSrv } from '@grafana/runtime'; +import { Button, useTheme2 } from '@grafana/ui'; + import { CrawlerStartButton } from './CrawlerStartButton'; interface CrawlerStatusMessage { diff --git a/public/app/features/admin/LicenseChrome.tsx b/public/app/features/admin/LicenseChrome.tsx index 1acf09e3696..ada9a83935d 100644 --- a/public/app/features/admin/LicenseChrome.tsx +++ b/public/app/features/admin/LicenseChrome.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { stylesFactory, useTheme } from '@grafana/ui'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { stylesFactory, useTheme } from '@grafana/ui'; const title = { fontWeight: 500, fontSize: '26px', lineHeight: '123%' }; diff --git a/public/app/features/admin/OrgRolePicker.tsx b/public/app/features/admin/OrgRolePicker.tsx index 88e30c8a547..ef6a158e4a5 100644 --- a/public/app/features/admin/OrgRolePicker.tsx +++ b/public/app/features/admin/OrgRolePicker.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { OrgRole } from '@grafana/data'; import { Select } from '@grafana/ui'; diff --git a/public/app/features/admin/ServerStats.test.tsx b/public/app/features/admin/ServerStats.test.tsx index 4f777c22e0a..eddea4f6068 100644 --- a/public/app/features/admin/ServerStats.test.tsx +++ b/public/app/features/admin/ServerStats.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { ServerStats } from './ServerStats'; import { ServerStat } from './state/apis'; diff --git a/public/app/features/admin/ServerStats.tsx b/public/app/features/admin/ServerStats.tsx index c883750e161..672d1ba2746 100644 --- a/public/app/features/admin/ServerStats.tsx +++ b/public/app/features/admin/ServerStats.tsx @@ -1,13 +1,16 @@ -import React, { useEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { CardContainer, LinkButton, useStyles2 } from '@grafana/ui'; +import React, { useEffect, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { CardContainer, LinkButton, useStyles2 } from '@grafana/ui'; import { AccessControlAction } from 'app/types'; -import { getServerStats, ServerStat } from './state/apis'; + import { contextSrv } from '../../core/services/context_srv'; import { Loader } from '../plugins/admin/components/Loader'; -import { config } from '@grafana/runtime'; + import { CrawlerStatus } from './CrawlerStatus'; +import { getServerStats, ServerStat } from './state/apis'; export const ServerStats = () => { const [stats, setStats] = useState(null); diff --git a/public/app/features/admin/UpgradePage.tsx b/public/app/features/admin/UpgradePage.tsx index a2bdc527c9a..3678cb244c6 100644 --- a/public/app/features/admin/UpgradePage.tsx +++ b/public/app/features/admin/UpgradePage.tsx @@ -1,12 +1,15 @@ +import { css } from '@emotion/css'; import React from 'react'; import { connect } from 'react-redux'; -import { css } from '@emotion/css'; -import { LinkButton, useStyles2 } from '@grafana/ui'; + import { GrafanaTheme2, NavModel } from '@grafana/data'; +import { LinkButton, useStyles2 } from '@grafana/ui'; + import Page from '../../core/components/Page/Page'; import { getNavModel } from '../../core/selectors/navModel'; -import { LicenseChrome } from './LicenseChrome'; import { StoreState } from '../../types'; + +import { LicenseChrome } from './LicenseChrome'; import { ServerStats } from './ServerStats'; interface Props { diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index 5a4d9c789fc..264e9ce943a 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -1,14 +1,19 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { NavModel } from '@grafana/data'; -import { getNavModel } from 'app/core/selectors/navModel'; import { featureEnabled } from '@grafana/runtime'; import Page from 'app/core/components/Page/Page'; -import { UserProfile } from './UserProfile'; -import { UserPermissions } from './UserPermissions'; -import { UserSessions } from './UserSessions'; -import { UserLdapSyncInfo } from './UserLdapSyncInfo'; +import { contextSrv } from 'app/core/core'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState, UserDTO, UserOrg, UserSession, SyncInfo, UserAdminError, AccessControlAction } from 'app/types'; + +import { UserLdapSyncInfo } from './UserLdapSyncInfo'; +import { UserOrgs } from './UserOrgs'; +import { UserPermissions } from './UserPermissions'; +import { UserProfile } from './UserProfile'; +import { UserSessions } from './UserSessions'; import { loadAdminUserPage, revokeSession, @@ -24,9 +29,6 @@ import { deleteOrgUser, syncLdapUser, } from './state/actions'; -import { UserOrgs } from './UserOrgs'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { contextSrv } from 'app/core/core'; interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> { navModel: NavModel; diff --git a/public/app/features/admin/UserCreatePage.tsx b/public/app/features/admin/UserCreatePage.tsx index 86a0c5f2b48..b98e82a3e6a 100644 --- a/public/app/features/admin/UserCreatePage.tsx +++ b/public/app/features/admin/UserCreatePage.tsx @@ -1,12 +1,14 @@ import React, { useCallback } from 'react'; import { connect } from 'react-redux'; -import { Form, Button, Input, Field } from '@grafana/ui'; +import { useHistory } from 'react-router-dom'; + import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import { StoreState } from '../../types'; -import { getNavModel } from '../../core/selectors/navModel'; +import { Form, Button, Input, Field } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; -import { useHistory } from 'react-router-dom'; + +import { getNavModel } from '../../core/selectors/navModel'; +import { StoreState } from '../../types'; interface UserCreatePageProps { navModel: NavModel; diff --git a/public/app/features/admin/UserLdapSyncInfo.tsx b/public/app/features/admin/UserLdapSyncInfo.tsx index 6ba53378e4b..7605ef90873 100644 --- a/public/app/features/admin/UserLdapSyncInfo.tsx +++ b/public/app/features/admin/UserLdapSyncInfo.tsx @@ -1,8 +1,9 @@ import React, { PureComponent } from 'react'; + import { dateTimeFormat } from '@grafana/data'; -import { AccessControlAction, SyncInfo, UserDTO } from 'app/types'; import { Button, LinkButton } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction, SyncInfo, UserDTO } from 'app/types'; interface Props { ldapSyncInfo: SyncInfo; diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index dfb7864ca73..7fa5809ae4f 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -1,6 +1,8 @@ -import React, { ComponentType, useEffect, useMemo, memo } from 'react'; import { css, cx } from '@emotion/css'; +import React, { ComponentType, useEffect, useMemo, memo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Icon, IconName, @@ -11,14 +13,15 @@ import { useStyles2, FilterInput, } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import Page from 'app/core/components/Page/Page'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { contextSrv } from 'app/core/core'; + +import PageLoader from '../../core/components/PageLoader/PageLoader'; import { getNavModel } from '../../core/selectors/navModel'; import { AccessControlAction, StoreState, Unit, UserDTO, UserFilter } from '../../types'; + import { changeFilter, changePage, changeQuery, fetchUsers } from './state/actions'; -import PageLoader from '../../core/components/PageLoader/PageLoader'; export interface FilterProps { filters: UserFilter[]; diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index ae9b8a21405..1531ab70df4 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -1,5 +1,7 @@ -import React, { PureComponent, ReactElement } from 'react'; import { css, cx } from '@emotion/css'; +import React, { PureComponent, ReactElement } from 'react'; + +import { GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; import { Button, ConfirmButton, @@ -14,13 +16,13 @@ import { useTheme, withTheme, } from '@grafana/ui'; -import { GrafanaTheme, GrafanaTheme2 } from '@grafana/data'; -import { AccessControlAction, Organization, OrgRole, UserDTO, UserOrg } from 'app/types'; -import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker'; -import { OrgRolePicker } from './OrgRolePicker'; -import { contextSrv } from 'app/core/core'; import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; import { fetchRoleOptions } from 'app/core/components/RolePicker/api'; +import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction, Organization, OrgRole, UserDTO, UserOrg } from 'app/types'; + +import { OrgRolePicker } from './OrgRolePicker'; interface Props { orgs: UserOrg[]; diff --git a/public/app/features/admin/UserPermissions.tsx b/public/app/features/admin/UserPermissions.tsx index f1888f1a739..41f6ac321ca 100644 --- a/public/app/features/admin/UserPermissions.tsx +++ b/public/app/features/admin/UserPermissions.tsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; + import { ConfirmButton, RadioButtonGroup, Icon } from '@grafana/ui'; -import { AccessControlAction } from 'app/types'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction } from 'app/types'; interface Props { isGrafanaAdmin: boolean; diff --git a/public/app/features/admin/UserProfile.tsx b/public/app/features/admin/UserProfile.tsx index 64f37b0e76b..fe9b7646db5 100644 --- a/public/app/features/admin/UserProfile.tsx +++ b/public/app/features/admin/UserProfile.tsx @@ -1,10 +1,11 @@ -import React, { FC, PureComponent, useRef, useState } from 'react'; -import { AccessControlAction, UserDTO } from 'app/types'; import { css, cx } from '@emotion/css'; -import { config } from 'app/core/config'; +import React, { FC, PureComponent, useRef, useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { Button, ConfirmButton, ConfirmModal, Input, LegacyInputStatus, stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction, UserDTO } from 'app/types'; interface Props { user: UserDTO; diff --git a/public/app/features/admin/UserSessions.tsx b/public/app/features/admin/UserSessions.tsx index cd7b52a7b62..4eccd8241ed 100644 --- a/public/app/features/admin/UserSessions.tsx +++ b/public/app/features/admin/UserSessions.tsx @@ -1,9 +1,10 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; -import { ConfirmButton, ConfirmModal, Button } from '@grafana/ui'; -import { AccessControlAction, UserSession } from 'app/types'; -import { contextSrv } from 'app/core/core'; import { withI18n, withI18nProps } from '@lingui/react'; +import React, { PureComponent } from 'react'; + +import { ConfirmButton, ConfirmModal, Button } from '@grafana/ui'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction, UserSession } from 'app/types'; interface Props extends withI18nProps { sessions: UserSession[]; diff --git a/public/app/features/admin/ldap/LdapConnectionStatus.tsx b/public/app/features/admin/ldap/LdapConnectionStatus.tsx index 1e79ecd80c9..6fbed0a9909 100644 --- a/public/app/features/admin/ldap/LdapConnectionStatus.tsx +++ b/public/app/features/admin/ldap/LdapConnectionStatus.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { Alert, Icon } from '@grafana/ui'; import { AppNotificationSeverity, LdapConnectionInfo, LdapServerInfo } from 'app/types'; diff --git a/public/app/features/admin/ldap/LdapPage.tsx b/public/app/features/admin/ldap/LdapPage.tsx index de23eba550b..28adbdc13ad 100644 --- a/public/app/features/admin/ldap/LdapPage.tsx +++ b/public/app/features/admin/ldap/LdapPage.tsx @@ -1,14 +1,14 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { NavModel } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; import { Alert, Button, LegacyForms } from '@grafana/ui'; const { FormField } = LegacyForms; -import { getNavModel } from 'app/core/selectors/navModel'; import Page from 'app/core/components/Page/Page'; -import { LdapConnectionStatus } from './LdapConnectionStatus'; -import { LdapSyncInfo } from './LdapSyncInfo'; -import { LdapUserInfo } from './LdapUserInfo'; +import { contextSrv } from 'app/core/core'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; import { AppNotificationSeverity, LdapError, @@ -18,6 +18,7 @@ import { LdapConnectionInfo, AccessControlAction, } from 'app/types'; + import { loadLdapState, loadLdapSyncStatus, @@ -25,8 +26,10 @@ import { clearUserError, clearUserMappingInfo, } from '../state/actions'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { contextSrv } from 'app/core/core'; + +import { LdapConnectionStatus } from './LdapConnectionStatus'; +import { LdapSyncInfo } from './LdapSyncInfo'; +import { LdapUserInfo } from './LdapUserInfo'; interface OwnProps extends GrafanaRouteComponentProps<{}, { username?: string }> { navModel: NavModel; diff --git a/public/app/features/admin/ldap/LdapSyncInfo.tsx b/public/app/features/admin/ldap/LdapSyncInfo.tsx index 91a89073bc3..aabcd79358b 100644 --- a/public/app/features/admin/ldap/LdapSyncInfo.tsx +++ b/public/app/features/admin/ldap/LdapSyncInfo.tsx @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; + import { dateTimeFormat } from '@grafana/data'; import { Button, Spinner } from '@grafana/ui'; import { SyncInfo } from 'app/types'; diff --git a/public/app/features/admin/ldap/LdapUserGroups.tsx b/public/app/features/admin/ldap/LdapUserGroups.tsx index b0b7da6c153..0fa361acbb5 100644 --- a/public/app/features/admin/ldap/LdapUserGroups.tsx +++ b/public/app/features/admin/ldap/LdapUserGroups.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { Tooltip, Icon } from '@grafana/ui'; import { LdapRole } from 'app/types'; diff --git a/public/app/features/admin/ldap/LdapUserInfo.tsx b/public/app/features/admin/ldap/LdapUserInfo.tsx index c1c3298ccb1..51228b7d4e5 100644 --- a/public/app/features/admin/ldap/LdapUserInfo.tsx +++ b/public/app/features/admin/ldap/LdapUserInfo.tsx @@ -1,9 +1,11 @@ import React, { FC } from 'react'; + +import { LdapUser } from 'app/types'; + +import { LdapUserGroups } from './LdapUserGroups'; import { LdapUserMappingInfo } from './LdapUserMappingInfo'; import { LdapUserPermissions } from './LdapUserPermissions'; -import { LdapUserGroups } from './LdapUserGroups'; import { LdapUserTeams } from './LdapUserTeams'; -import { LdapUser } from 'app/types'; interface Props { ldapUser: LdapUser; diff --git a/public/app/features/admin/ldap/LdapUserMappingInfo.tsx b/public/app/features/admin/ldap/LdapUserMappingInfo.tsx index 26998042faf..8376ba74a07 100644 --- a/public/app/features/admin/ldap/LdapUserMappingInfo.tsx +++ b/public/app/features/admin/ldap/LdapUserMappingInfo.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { LdapUserInfo } from 'app/types'; interface Props { diff --git a/public/app/features/admin/ldap/LdapUserPermissions.tsx b/public/app/features/admin/ldap/LdapUserPermissions.tsx index 3ee94d86fdb..526d4299f77 100644 --- a/public/app/features/admin/ldap/LdapUserPermissions.tsx +++ b/public/app/features/admin/ldap/LdapUserPermissions.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; -import { LdapPermissions } from 'app/types'; + import { Icon } from '@grafana/ui'; +import { LdapPermissions } from 'app/types'; interface Props { permissions: LdapPermissions; diff --git a/public/app/features/admin/ldap/LdapUserTeams.tsx b/public/app/features/admin/ldap/LdapUserTeams.tsx index 38a76e66542..08c601c9159 100644 --- a/public/app/features/admin/ldap/LdapUserTeams.tsx +++ b/public/app/features/admin/ldap/LdapUserTeams.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { Tooltip, Icon } from '@grafana/ui'; import { LdapTeam } from 'app/types'; diff --git a/public/app/features/admin/state/actions.ts b/public/app/features/admin/state/actions.ts index 2297783226d..d21672fce71 100644 --- a/public/app/features/admin/state/actions.ts +++ b/public/app/features/admin/state/actions.ts @@ -1,6 +1,10 @@ -import config from 'app/core/config'; +import { debounce } from 'lodash'; + import { dateTimeFormatTimeAgo } from '@grafana/data'; import { featureEnabled, getBackendSrv, locationService } from '@grafana/runtime'; +import config from 'app/core/config'; +import { contextSrv } from 'app/core/core'; +import { accessControlQueryParam } from 'app/core/utils/accessControl'; import { ThunkResult, LdapUser, UserSession, UserDTO, AccessControlAction, UserFilter } from 'app/types'; import { @@ -23,9 +27,6 @@ import { usersFetchBegin, usersFetchEnd, } from './reducers'; -import { debounce } from 'lodash'; -import { contextSrv } from 'app/core/core'; -import { accessControlQueryParam } from 'app/core/utils/accessControl'; // UserAdminPage export function loadAdminUserPage(userId: number): ThunkResult { diff --git a/public/app/features/admin/state/reducers.test.ts b/public/app/features/admin/state/reducers.test.ts index 6056194185c..ac9b7f37ae7 100644 --- a/public/app/features/admin/state/reducers.test.ts +++ b/public/app/features/admin/state/reducers.test.ts @@ -1,4 +1,7 @@ import { reducerTester } from 'test/core/redux/reducerTester'; + +import { LdapState, LdapUser, UserAdminState, UserDTO, UserListAdminState } from 'app/types'; + import { clearUserMappingInfoAction, ldapConnectionInfoLoadedAction, @@ -12,8 +15,8 @@ import { userSessionsLoadedAction, userListAdminReducer, queryChanged, + filterChanged, } from './reducers'; -import { LdapState, LdapUser, UserAdminState, UserDTO, UserListAdminState } from 'app/types'; const makeInitialLdapState = (): LdapState => ({ connectionInfo: [], @@ -287,4 +290,22 @@ describe('User List Admin reducer', () => { }); }); }); + + describe('When filter changed', () => { + it('should reset page to 0', () => { + const initialState = { + ...makeInitialUserListAdminState(), + page: 3, + }; + + reducerTester() + .givenReducer(userListAdminReducer, initialState) + .whenActionIsDispatched(filterChanged({ test: true })) + .thenStateShouldEqual({ + ...makeInitialUserListAdminState(), + page: 0, + filters: expect.arrayContaining([{ test: true }]), + }); + }); + }); }); diff --git a/public/app/features/admin/state/reducers.ts b/public/app/features/admin/state/reducers.ts index 63000d85166..025f95807aa 100644 --- a/public/app/features/admin/state/reducers.ts +++ b/public/app/features/admin/state/reducers.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + import { LdapConnectionInfo, LdapError, @@ -178,11 +179,13 @@ export const userListAdminSlice = createSlice({ if (state.filters.some((filter) => filter.name === name)) { return { ...state, + page: 0, filters: state.filters.map((filter) => (filter.name === name ? { ...filter, value } : filter)), }; } return { ...state, + page: 0, filters: [...state.filters, action.payload], }; }, diff --git a/public/app/features/admin/utils.ts b/public/app/features/admin/utils.ts new file mode 100644 index 00000000000..f4bdf531730 --- /dev/null +++ b/public/app/features/admin/utils.ts @@ -0,0 +1,8 @@ +import { config } from '@grafana/runtime/src'; + +export function isTrial() { + const expiry = config.licenseInfo?.trialExpiry; + return !!(expiry && expiry > 0); +} + +export const highlightTrial = () => isTrial() && config.featureToggles.featureHighlights; diff --git a/public/app/features/alerting/AlertHowToModal.tsx b/public/app/features/alerting/AlertHowToModal.tsx index c4bcbda7db1..4ebddb0c089 100644 --- a/public/app/features/alerting/AlertHowToModal.tsx +++ b/public/app/features/alerting/AlertHowToModal.tsx @@ -1,6 +1,7 @@ -import { Modal, VerticalGroup } from '@grafana/ui'; import React from 'react'; +import { Modal, VerticalGroup } from '@grafana/ui'; + export interface AlertHowToModalProps { onDismiss: () => void; } diff --git a/public/app/features/alerting/AlertRuleItem.test.tsx b/public/app/features/alerting/AlertRuleItem.test.tsx index e619e713334..a779b48c74b 100644 --- a/public/app/features/alerting/AlertRuleItem.test.tsx +++ b/public/app/features/alerting/AlertRuleItem.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; +import React from 'react'; + import AlertRuleItem, { Props } from './AlertRuleItem'; const setup = (propOverrides?: object) => { diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx index 53468797393..a1b1f93e2bc 100644 --- a/public/app/features/alerting/AlertRuleItem.tsx +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -1,7 +1,9 @@ import React, { useCallback } from 'react'; // @ts-ignore import Highlighter from 'react-highlight-words'; + import { Icon, IconName, Button, LinkButton, Card } from '@grafana/ui'; + import { AlertRule } from '../../types'; export interface Props { diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index 935b3d309db..ffa28f2cef2 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -1,15 +1,18 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { AlertRuleListUnconnected, Props } from './AlertRuleList'; -import { AlertRule } from '../../types'; -import appEvents from '../../core/app_events'; -import { NavModel } from '@grafana/data'; -import { setSearchQuery } from './state/reducers'; +import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; + +import { NavModel } from '@grafana/data'; import { locationService } from '@grafana/runtime'; +import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; + +import appEvents from '../../core/app_events'; +import { AlertRule } from '../../types'; import { ShowModalReactEvent } from '../../types/events'; + import { AlertHowToModal } from './AlertHowToModal'; +import { AlertRuleListUnconnected, Props } from './AlertRuleList'; +import { setSearchQuery } from './state/reducers'; jest.mock('../../core/app_events', () => ({ publish: jest.fn(), diff --git a/public/app/features/alerting/AlertRuleList.tsx b/public/app/features/alerting/AlertRuleList.tsx index 2085c76b96d..72349521f84 100644 --- a/public/app/features/alerting/AlertRuleList.tsx +++ b/public/app/features/alerting/AlertRuleList.tsx @@ -1,20 +1,23 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import Page from 'app/core/components/Page/Page'; -import AlertRuleItem from './AlertRuleItem'; -import appEvents from 'app/core/app_events'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { AlertRule, StoreState } from 'app/types'; -import { getAlertRulesAsync, togglePauseAlertRule } from './state/actions'; -import { getAlertRuleItems, getSearchQuery } from './state/selectors'; + import { SelectableValue } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; -import { setSearchQuery } from './state/reducers'; import { Button, FilterInput, LinkButton, Select, VerticalGroup } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; +import Page from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { AlertRule, StoreState } from 'app/types'; + import { ShowModalReactEvent } from '../../types/events'; + import { AlertHowToModal } from './AlertHowToModal'; +import AlertRuleItem from './AlertRuleItem'; import { UnifiedAlertingPromotion } from './components/UnifiedAlertingPromotion'; +import { getAlertRulesAsync, togglePauseAlertRule } from './state/actions'; +import { setSearchQuery } from './state/reducers'; +import { getAlertRuleItems, getSearchQuery } from './state/selectors'; function mapStateToProps(state: StoreState) { return { diff --git a/public/app/features/alerting/AlertTab.tsx b/public/app/features/alerting/AlertTab.tsx index 2c75d68fc49..0c2c8f1f408 100644 --- a/public/app/features/alerting/AlertTab.tsx +++ b/public/app/features/alerting/AlertTab.tsx @@ -1,19 +1,22 @@ import React, { PureComponent } from 'react'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { Alert, Button, ConfirmModal, Container, CustomScrollbar, HorizontalGroup, IconName, Modal } from '@grafana/ui'; + +import { EventBusSrv } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { AngularComponent, config, getAngularLoader, getDataSourceSrv } from '@grafana/runtime'; -import { getAlertingValidationMessage } from './getAlertingValidationMessage'; +import { Alert, Button, ConfirmModal, Container, CustomScrollbar, HorizontalGroup, IconName, Modal } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import StateHistory from './StateHistory'; +import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { AppNotificationSeverity, StoreState } from 'app/types'; + +import { AlertState } from '../../plugins/datasource/alertmanager/types'; +import { PanelNotSupported } from '../dashboard/components/PanelEditor/PanelNotSupported'; import { DashboardModel } from '../dashboard/state/DashboardModel'; import { PanelModel } from '../dashboard/state/PanelModel'; + +import StateHistory from './StateHistory'; import { TestRuleResult } from './TestRuleResult'; -import { AppNotificationSeverity, StoreState } from 'app/types'; -import { PanelNotSupported } from '../dashboard/components/PanelEditor/PanelNotSupported'; -import { AlertState } from '../../plugins/datasource/alertmanager/types'; -import { EventBusSrv } from '@grafana/data'; -import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { getAlertingValidationMessage } from './getAlertingValidationMessage'; interface AngularPanelController { _enableAlert: () => void; diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 804490fe994..df717f79ee7 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -1,19 +1,22 @@ import { find, map, reduce, remove } from 'lodash'; -import coreModule from 'app/angular/core_module'; -import { ThresholdMapper } from './state/ThresholdMapper'; -import { QueryPart } from 'app/features/alerting/state/query_part'; -import alertDef from './state/alertDef'; -import config from 'app/core/config'; -import appEvents from 'app/core/app_events'; + +import { DataQuery, DataSourceApi, rangeUtil } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; +import coreModule from 'app/angular/core_module'; +import { promiseToDigest } from 'app/angular/promiseToDigest'; +import appEvents from 'app/core/app_events'; +import config from 'app/core/config'; +import { QueryPart } from 'app/features/alerting/state/query_part'; +import { PanelModel } from 'app/features/dashboard/state'; +import { CoreEvents } from 'app/types'; + +import { ShowConfirmModalEvent } from '../../types/events'; import { DashboardSrv } from '../dashboard/services/DashboardSrv'; import { DatasourceSrv } from '../plugins/datasource_srv'; -import { DataQuery, DataSourceApi, rangeUtil } from '@grafana/data'; -import { PanelModel } from 'app/features/dashboard/state'; + import { getDefaultCondition } from './getAlertingValidationMessage'; -import { CoreEvents } from 'app/types'; -import { promiseToDigest } from 'app/angular/promiseToDigest'; -import { ShowConfirmModalEvent } from '../../types/events'; +import { ThresholdMapper } from './state/ThresholdMapper'; +import alertDef from './state/alertDef'; export class AlertTabCtrl { panel: PanelModel; diff --git a/public/app/features/alerting/AlertTabIndex.tsx b/public/app/features/alerting/AlertTabIndex.tsx index e1e1ec95c7f..ec815490849 100644 --- a/public/app/features/alerting/AlertTabIndex.tsx +++ b/public/app/features/alerting/AlertTabIndex.tsx @@ -1,4 +1,5 @@ import { config } from '@grafana/runtime'; + import { AlertTab } from './AlertTab'; import { PanelAlertTabContent } from './unified/PanelAlertTabContent'; diff --git a/public/app/features/alerting/EditNotificationChannelPage.tsx b/public/app/features/alerting/EditNotificationChannelPage.tsx index dbbce7e46f0..45aba248222 100644 --- a/public/app/features/alerting/EditNotificationChannelPage.tsx +++ b/public/app/features/alerting/EditNotificationChannelPage.tsx @@ -1,17 +1,19 @@ import React, { PureComponent } from 'react'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; + import { NavModel } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Form, Spinner } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import { connectWithCleanUp } from 'app/core/components/connectWithCleanUp'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { NotificationChannelType, NotificationChannelDTO, StoreState } from 'app/types'; + import { NotificationChannelForm } from './components/NotificationChannelForm'; import { loadNotificationChannel, testNotificationChannel, updateNotificationChannel } from './state/actions'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { mapChannelsToSelectableValue, transformSubmitData, transformTestData } from './utils/notificationChannels'; -import { NotificationChannelType, NotificationChannelDTO, StoreState } from 'app/types'; import { resetSecureField } from './state/reducers'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { mapChannelsToSelectableValue, transformSubmitData, transformTestData } from './utils/notificationChannels'; interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> {} diff --git a/public/app/features/alerting/FeatureTogglePage.tsx b/public/app/features/alerting/FeatureTogglePage.tsx index 57fb28b554a..63f62796f94 100644 --- a/public/app/features/alerting/FeatureTogglePage.tsx +++ b/public/app/features/alerting/FeatureTogglePage.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import Page from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; diff --git a/public/app/features/alerting/NewNotificationChannelPage.tsx b/public/app/features/alerting/NewNotificationChannelPage.tsx index 247b5641bff..418029fb2e5 100644 --- a/public/app/features/alerting/NewNotificationChannelPage.tsx +++ b/public/app/features/alerting/NewNotificationChannelPage.tsx @@ -1,19 +1,22 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { config } from '@grafana/runtime'; import { Form } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; +import { getNavModel } from 'app/core/selectors/navModel'; + +import { NotificationChannelDTO, StoreState } from '../../types'; + import { NotificationChannelForm } from './components/NotificationChannelForm'; +import { createNotificationChannel, loadNotificationTypes, testNotificationChannel } from './state/actions'; +import { resetSecureField } from './state/reducers'; import { defaultValues, mapChannelsToSelectableValue, transformSubmitData, transformTestData, } from './utils/notificationChannels'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { createNotificationChannel, loadNotificationTypes, testNotificationChannel } from './state/actions'; -import { NotificationChannelDTO, StoreState } from '../../types'; -import { resetSecureField } from './state/reducers'; class NewNotificationChannelPage extends PureComponent { componentDidMount() { diff --git a/public/app/features/alerting/NotificationsListPage.tsx b/public/app/features/alerting/NotificationsListPage.tsx index e391e11a875..9a5e5b3f4eb 100644 --- a/public/app/features/alerting/NotificationsListPage.tsx +++ b/public/app/features/alerting/NotificationsListPage.tsx @@ -1,12 +1,14 @@ import React, { useState, FC, useEffect } from 'react'; +import { useAsyncFn } from 'react-use'; + +import { getBackendSrv } from '@grafana/runtime'; +import { HorizontalGroup, Button, LinkButton } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import Page from 'app/core/components/Page/Page'; -import { getBackendSrv } from '@grafana/runtime'; -import { useAsyncFn } from 'react-use'; import { appEvents } from 'app/core/core'; import { useNavModel } from 'app/core/hooks/useNavModel'; -import { HorizontalGroup, Button, LinkButton } from '@grafana/ui'; import { AlertNotification } from 'app/types/alerting'; + import { ShowConfirmModalEvent } from '../../types/events'; const NotificationsListPage: FC = () => { diff --git a/public/app/features/alerting/StateHistory.tsx b/public/app/features/alerting/StateHistory.tsx index c6fc1a2bed2..158cc30fad6 100644 --- a/public/app/features/alerting/StateHistory.tsx +++ b/public/app/features/alerting/StateHistory.tsx @@ -1,10 +1,12 @@ +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; + import { getBackendSrv } from '@grafana/runtime'; import { Icon, ConfirmButton, Button } from '@grafana/ui'; -import alertDef from './state/alertDef'; import { DashboardModel } from '../dashboard/state/DashboardModel'; -import { css } from '@emotion/css'; + +import alertDef from './state/alertDef'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/alerting/TestRuleResult.test.tsx b/public/app/features/alerting/TestRuleResult.test.tsx index 984ea78c36e..46ac94f248f 100644 --- a/public/app/features/alerting/TestRuleResult.test.tsx +++ b/public/app/features/alerting/TestRuleResult.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { TestRuleResult, Props } from './TestRuleResult'; -import { DashboardModel, PanelModel } from '../dashboard/state'; import { shallow } from 'enzyme'; +import React from 'react'; + +import { DashboardModel, PanelModel } from '../dashboard/state'; + +import { TestRuleResult, Props } from './TestRuleResult'; jest.mock('@grafana/runtime', () => { const original = jest.requireActual('@grafana/runtime'); diff --git a/public/app/features/alerting/TestRuleResult.tsx b/public/app/features/alerting/TestRuleResult.tsx index 76669a9db90..bbed594e7d3 100644 --- a/public/app/features/alerting/TestRuleResult.tsx +++ b/public/app/features/alerting/TestRuleResult.tsx @@ -1,9 +1,11 @@ import React, { PureComponent } from 'react'; + +import { AppEvents } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; import { LoadingPlaceholder, JSONFormatter, Icon, HorizontalGroup, ClipboardButton } from '@grafana/ui'; import appEvents from 'app/core/app_events'; + import { DashboardModel, PanelModel } from '../dashboard/state'; -import { getBackendSrv } from '@grafana/runtime'; -import { AppEvents } from '@grafana/data'; export interface Props { dashboard: DashboardModel; diff --git a/public/app/features/alerting/components/BasicSettings.tsx b/public/app/features/alerting/components/BasicSettings.tsx index 75ccb993c60..dc65562ba0f 100644 --- a/public/app/features/alerting/components/BasicSettings.tsx +++ b/public/app/features/alerting/components/BasicSettings.tsx @@ -1,10 +1,13 @@ import React, { FC } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Field, Input, InputControl, Select } from '@grafana/ui'; -import { NotificationChannelOptions } from './NotificationChannelOptions'; -import { NotificationSettingsProps } from './NotificationChannelForm'; + import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types'; +import { NotificationSettingsProps } from './NotificationChannelForm'; +import { NotificationChannelOptions } from './NotificationChannelOptions'; + interface Props extends NotificationSettingsProps { selectedChannel: NotificationChannelType; channels: Array>; diff --git a/public/app/features/alerting/components/ChannelSettings.tsx b/public/app/features/alerting/components/ChannelSettings.tsx index 9aa90aa87d8..292110d723d 100644 --- a/public/app/features/alerting/components/ChannelSettings.tsx +++ b/public/app/features/alerting/components/ChannelSettings.tsx @@ -1,9 +1,12 @@ import React, { FC } from 'react'; + import { Alert, CollapsableSection } from '@grafana/ui'; -import { NotificationChannelOptions } from './NotificationChannelOptions'; -import { NotificationSettingsProps } from './NotificationChannelForm'; + import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types'; +import { NotificationSettingsProps } from './NotificationChannelForm'; +import { NotificationChannelOptions } from './NotificationChannelOptions'; + interface Props extends NotificationSettingsProps { selectedChannel: NotificationChannelType; secureFields: NotificationChannelSecureFields; diff --git a/public/app/features/alerting/components/NotificationChannelForm.tsx b/public/app/features/alerting/components/NotificationChannelForm.tsx index e05cf8e5bbd..ba471a9eee5 100644 --- a/public/app/features/alerting/components/NotificationChannelForm.tsx +++ b/public/app/features/alerting/components/NotificationChannelForm.tsx @@ -1,13 +1,15 @@ -import React, { FC, useEffect } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useEffect } from 'react'; + import { GrafanaTheme, SelectableValue } from '@grafana/data'; import { Button, FormAPI, HorizontalGroup, stylesFactory, useTheme, Spinner } from '@grafana/ui'; +import config from 'app/core/config'; + import { NotificationChannelType, NotificationChannelDTO, NotificationChannelSecureFields } from '../../../types'; -import { NotificationSettings } from './NotificationSettings'; + import { BasicSettings } from './BasicSettings'; import { ChannelSettings } from './ChannelSettings'; - -import config from 'app/core/config'; +import { NotificationSettings } from './NotificationSettings'; interface Props extends Pick, 'control' | 'errors' | 'register' | 'watch' | 'getValues'> { diff --git a/public/app/features/alerting/components/NotificationChannelOptions.tsx b/public/app/features/alerting/components/NotificationChannelOptions.tsx index 9cdb59a61d5..2bd4cb7918b 100644 --- a/public/app/features/alerting/components/NotificationChannelOptions.tsx +++ b/public/app/features/alerting/components/NotificationChannelOptions.tsx @@ -1,9 +1,12 @@ import React, { FC } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Button, Checkbox, Field, Input } from '@grafana/ui'; -import { OptionElement } from './OptionElement'; + import { NotificationChannelDTO, NotificationChannelOption, NotificationChannelSecureFields } from '../../../types'; + import { NotificationSettingsProps } from './NotificationChannelForm'; +import { OptionElement } from './OptionElement'; interface Props extends NotificationSettingsProps { selectedChannelOptions: NotificationChannelOption[]; diff --git a/public/app/features/alerting/components/NotificationSettings.tsx b/public/app/features/alerting/components/NotificationSettings.tsx index 9972becc5b8..2e9ea0629e4 100644 --- a/public/app/features/alerting/components/NotificationSettings.tsx +++ b/public/app/features/alerting/components/NotificationSettings.tsx @@ -1,5 +1,7 @@ import React, { FC } from 'react'; + import { Checkbox, CollapsableSection, Field, InfoBox, Input } from '@grafana/ui'; + import { NotificationSettingsProps } from './NotificationChannelForm'; interface Props extends NotificationSettingsProps { diff --git a/public/app/features/alerting/components/OptionElement.tsx b/public/app/features/alerting/components/OptionElement.tsx index 4c949202de5..aa0de6ad70d 100644 --- a/public/app/features/alerting/components/OptionElement.tsx +++ b/public/app/features/alerting/components/OptionElement.tsx @@ -1,5 +1,7 @@ import React, { FC } from 'react'; + import { FormAPI, Input, InputControl, Select, TextArea } from '@grafana/ui'; + import { NotificationChannelOption } from '../../../types'; interface Props extends Pick, 'register' | 'control'> { diff --git a/public/app/features/alerting/components/UnifiedAlertingPromotion.test.tsx b/public/app/features/alerting/components/UnifiedAlertingPromotion.test.tsx index 1a5df7e6527..b6d03473696 100644 --- a/public/app/features/alerting/components/UnifiedAlertingPromotion.test.tsx +++ b/public/app/features/alerting/components/UnifiedAlertingPromotion.test.tsx @@ -1,7 +1,7 @@ -import React from 'react'; - import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { UnifiedAlertingPromotion, LOCAL_STORAGE_KEY } from './UnifiedAlertingPromotion'; describe('Unified Alerting promotion', () => { @@ -14,12 +14,12 @@ describe('Unified Alerting promotion', () => { expect(screen.queryByText('Try out the Grafana 8 alerting!')).toBeInTheDocument(); }); - it('should be hidden if dismissed', () => { + it('should be hidden if dismissed', async () => { const promotion = render(); expect(window.localStorage.getItem(LOCAL_STORAGE_KEY)).toBe('true'); const dismissButton = promotion.getByRole('button'); - userEvent.click(dismissButton); + await userEvent.click(dismissButton); expect(screen.queryByText('Try out the Grafana 8 alerting!')).not.toBeInTheDocument(); expect(window.localStorage.getItem(LOCAL_STORAGE_KEY)).toBe('false'); diff --git a/public/app/features/alerting/components/UnifiedAlertingPromotion.tsx b/public/app/features/alerting/components/UnifiedAlertingPromotion.tsx index d3a369494c9..2d89487dd42 100644 --- a/public/app/features/alerting/components/UnifiedAlertingPromotion.tsx +++ b/public/app/features/alerting/components/UnifiedAlertingPromotion.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react'; +import { useLocalStorage } from 'react-use'; import { Alert } from '@grafana/ui'; -import { useLocalStorage } from 'react-use'; export const LOCAL_STORAGE_KEY = 'grafana.legacyalerting.unifiedalertingpromo'; diff --git a/public/app/features/alerting/getAlertingValidationMessage.test.ts b/public/app/features/alerting/getAlertingValidationMessage.test.ts index 7b55785ca99..3b62e2ea13f 100644 --- a/public/app/features/alerting/getAlertingValidationMessage.test.ts +++ b/public/app/features/alerting/getAlertingValidationMessage.test.ts @@ -1,4 +1,3 @@ -import { DataSourceSrv } from '@grafana/runtime'; import { DataSourceApi, PluginMeta, @@ -6,8 +5,10 @@ import { DataSourceInstanceSettings, DataSourceRef, } from '@grafana/data'; +import { DataSourceSrv } from '@grafana/runtime'; import { ElasticsearchQuery } from '../../plugins/datasource/elasticsearch/types'; + import { getAlertingValidationMessage } from './getAlertingValidationMessage'; describe('getAlertingValidationMessage', () => { diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index d8eaa9cd338..333a5cbce42 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -1,11 +1,13 @@ +import { uniq } from 'lodash'; +import React from 'react'; +import { Redirect } from 'react-router-dom'; + import { OrgRole } from '@grafana/data'; import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; import { config } from 'app/core/config'; import { RouteDescriptor } from 'app/core/navigation/types'; import { AccessControlAction } from 'app/types'; -import { uniq } from 'lodash'; -import React from 'react'; -import { Redirect } from 'react-router-dom'; + import { evaluateAccess } from './unified/utils/access-control'; const commonRoutes: RouteDescriptor[] = [ diff --git a/public/app/features/alerting/state/actions.ts b/public/app/features/alerting/state/actions.ts index 56b7877fc80..11634d3620a 100644 --- a/public/app/features/alerting/state/actions.ts +++ b/public/app/features/alerting/state/actions.ts @@ -1,8 +1,9 @@ import { getBackendSrv, locationService } from '@grafana/runtime'; -import { loadAlertRules, loadedAlertRules, notificationChannelLoaded, setNotificationChannels } from './reducers'; -import { AlertRuleDTO, NotifierDTO, ThunkResult } from 'app/types'; -import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; import { notifyApp } from 'app/core/actions'; +import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; +import { AlertRuleDTO, NotifierDTO, ThunkResult } from 'app/types'; + +import { loadAlertRules, loadedAlertRules, notificationChannelLoaded, setNotificationChannels } from './reducers'; export function getAlertRulesAsync(options: { state: string }): ThunkResult { return async (dispatch) => { diff --git a/public/app/features/alerting/state/alertDef.ts b/public/app/features/alerting/state/alertDef.ts index 4851974b39e..de4f61cf7a6 100644 --- a/public/app/features/alerting/state/alertDef.ts +++ b/public/app/features/alerting/state/alertDef.ts @@ -1,4 +1,5 @@ import { isArray, reduce } from 'lodash'; + import { QueryPartDef, QueryPart } from 'app/features/alerting/state/query_part'; const alertQueryDef = new QueryPartDef({ diff --git a/public/app/features/alerting/state/reducers.test.ts b/public/app/features/alerting/state/reducers.test.ts index 5fe86af7b9c..a0e2f8d174f 100644 --- a/public/app/features/alerting/state/reducers.test.ts +++ b/public/app/features/alerting/state/reducers.test.ts @@ -1,4 +1,8 @@ import { dateTime } from '@grafana/data'; +import { AlertRuleDTO, AlertRulesState, NotificationChannelState, NotifierDTO } from 'app/types'; + +import { reducerTester } from '../../../../test/core/redux/reducerTester'; + import { alertRulesReducer, initialChannelState, @@ -9,8 +13,6 @@ import { setSearchQuery, notificationChannelLoaded, } from './reducers'; -import { AlertRuleDTO, AlertRulesState, NotificationChannelState, NotifierDTO } from 'app/types'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; describe('Alert rules', () => { const realDateNow = Date.now.bind(global.Date); diff --git a/public/app/features/alerting/state/reducers.ts b/public/app/features/alerting/state/reducers.ts index cee611ab40e..1cbe4d91541 100644 --- a/public/app/features/alerting/state/reducers.ts +++ b/public/app/features/alerting/state/reducers.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + import { dateTime } from '@grafana/data'; -import alertDef from './alertDef'; import { AlertRule, AlertRuleDTO, @@ -9,8 +9,11 @@ import { NotificationChannelState, NotifierDTO, } from 'app/types'; + import unifiedAlertingReducer from '../unified/state/reducers'; +import alertDef from './alertDef'; + export const initialState: AlertRulesState = { items: [], searchQuery: '', diff --git a/public/app/features/alerting/unified/Admin.tsx b/public/app/features/alerting/unified/Admin.tsx index 21a534cdca6..28a2a518763 100644 --- a/public/app/features/alerting/unified/Admin.tsx +++ b/public/app/features/alerting/unified/Admin.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import AlertmanagerConfig from './components/admin/AlertmanagerConfig'; import { ExternalAlertmanagers } from './components/admin/ExternalAlertmanagers'; diff --git a/public/app/features/alerting/unified/AlertGroups.test.tsx b/public/app/features/alerting/unified/AlertGroups.test.tsx index 765af678612..0c46f97e58d 100644 --- a/public/app/features/alerting/unified/AlertGroups.test.tsx +++ b/public/app/features/alerting/unified/AlertGroups.test.tsx @@ -1,15 +1,17 @@ -import React from 'react'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { fetchAlertGroups } from './api/alertmanager'; import { byRole, byTestId, byText } from 'testing-library-selector'; + +import { locationService, setDataSourceSrv } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; + import AlertGroups from './AlertGroups'; +import { fetchAlertGroups } from './api/alertmanager'; import { mockAlertGroup, mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv } from './mocks'; import { DataSourceType } from './utils/datasource'; -import userEvent from '@testing-library/user-event'; jest.mock('./api/alertmanager'); jest.mock('app/core/services/context_srv', () => ({ @@ -83,10 +85,10 @@ describe('AlertGroups', () => { expect(groups[0]).toHaveTextContent('No grouping'); expect(groups[1]).toHaveTextContent('severity=warningregion=US-Central'); - userEvent.click(ui.groupCollapseToggle.get(groups[0])); + await userEvent.click(ui.groupCollapseToggle.get(groups[0])); expect(ui.groupTable.get()).toBeDefined(); - userEvent.click(ui.collapseToggle.get(ui.groupTable.get())); + await userEvent.click(ui.collapseToggle.get(ui.groupTable.get())); expect(ui.silenceButton.get(ui.groupTable.get())).toBeDefined(); expect(ui.sourceButton.get(ui.groupTable.get())).toBeDefined(); }); @@ -118,7 +120,7 @@ describe('AlertGroups', () => { expect(groups[1]).toHaveTextContent('region=EMEA'); expect(groups[2]).toHaveTextContent('region=APAC'); - userEvent.type(groupByInput, 'appName{enter}'); + await userEvent.type(groupByInput, 'appName{enter}'); await waitFor(() => expect(groupByWrapper).toHaveTextContent('appName')); @@ -130,10 +132,10 @@ describe('AlertGroups', () => { expect(groups[1]).toHaveTextContent('appName=auth'); expect(groups[2]).toHaveTextContent('appName=frontend'); - userEvent.click(ui.clearButton.get()); + await userEvent.click(ui.clearButton.get()); await waitFor(() => expect(groupByWrapper).not.toHaveTextContent('appName')); - userEvent.type(groupByInput, 'env{enter}'); + await userEvent.type(groupByInput, 'env{enter}'); await waitFor(() => expect(groupByWrapper).toHaveTextContent('env')); groups = ui.group.getAll(); @@ -142,10 +144,10 @@ describe('AlertGroups', () => { expect(groups[0]).toHaveTextContent('env=production'); expect(groups[1]).toHaveTextContent('env=staging'); - userEvent.click(ui.clearButton.get()); + await userEvent.click(ui.clearButton.get()); await waitFor(() => expect(groupByWrapper).not.toHaveTextContent('env')); - userEvent.type(groupByInput, 'uniqueLabel{enter}'); + await userEvent.type(groupByInput, 'uniqueLabel{enter}'); await waitFor(() => expect(groupByWrapper).toHaveTextContent('uniqueLabel')); groups = ui.group.getAll(); diff --git a/public/app/features/alerting/unified/AlertGroups.tsx b/public/app/features/alerting/unified/AlertGroups.tsx index b009207fd28..2513f9788eb 100644 --- a/public/app/features/alerting/unified/AlertGroups.tsx +++ b/public/app/features/alerting/unified/AlertGroups.tsx @@ -1,23 +1,22 @@ +import { css } from '@emotion/css'; import React, { useEffect } from 'react'; import { useDispatch } from 'react-redux'; -import { Alert, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; + import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { AlertGroup } from './components/alert-groups/AlertGroup'; import { AlertGroupFilter } from './components/alert-groups/AlertGroupFilter'; -import { fetchAlertGroupsAction } from './state/actions'; - -import { initialAsyncRequestState } from './utils/redux'; -import { getFiltersFromUrlParams } from './utils/misc'; -import { NOTIFICATIONS_POLL_INTERVAL_MS } from './utils/constants'; - import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; -import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; -import { useGroupedAlerts } from './hooks/useGroupedAlerts'; import { useFilteredAmGroups } from './hooks/useFilteredAmGroups'; -import { css } from '@emotion/css'; +import { useGroupedAlerts } from './hooks/useGroupedAlerts'; +import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; +import { fetchAlertGroupsAction } from './state/actions'; +import { NOTIFICATIONS_POLL_INTERVAL_MS } from './utils/constants'; +import { getFiltersFromUrlParams } from './utils/misc'; +import { initialAsyncRequestState } from './utils/redux'; const AlertGroups = () => { const [alertManagerSourceName] = useAlertManagerSourceName(); diff --git a/public/app/features/alerting/unified/AlertsFolderView.test.tsx b/public/app/features/alerting/unified/AlertsFolderView.test.tsx index 940bd63d141..bb28d3984ec 100644 --- a/public/app/features/alerting/unified/AlertsFolderView.test.tsx +++ b/public/app/features/alerting/unified/AlertsFolderView.test.tsx @@ -1,12 +1,14 @@ import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { configureStore } from 'app/store/configureStore'; -import { FolderState } from 'app/types'; -import { CombinedRuleNamespace } from 'app/types/unified-alerting'; import React from 'react'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import { byTestId } from 'testing-library-selector'; + +import { configureStore } from 'app/store/configureStore'; +import { FolderState } from 'app/types'; +import { CombinedRuleNamespace } from 'app/types/unified-alerting'; + import { AlertsFolderView } from './AlertsFolderView'; import { mockCombinedRule } from './mocks'; import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; @@ -116,7 +118,7 @@ describe('AlertsFolderView tests', () => { expect(ui.ruleList.row.queryAll()).toHaveLength(0); }); - it('Should filter alert rules by the name, case insensitive', () => { + it('Should filter alert rules by the name, case insensitive', async () => { // Arrange const store = configureStore(); const folder = mockFolder(); @@ -143,14 +145,14 @@ describe('AlertsFolderView tests', () => { ); - userEvent.type(ui.filter.name.get(), 'cpu'); + await userEvent.type(ui.filter.name.get(), 'cpu'); // Assert expect(ui.ruleList.row.queryAll()).toHaveLength(1); expect(ui.ruleList.row.get()).toHaveTextContent('CPU Alert'); }); - it('Should filter alert rule by labels', () => { + it('Should filter alert rule by labels', async () => { // Arrange const store = configureStore(); const folder = mockFolder(); @@ -180,7 +182,7 @@ describe('AlertsFolderView tests', () => { ); - userEvent.type(ui.filter.label.get(), 'severity=critical'); + await userEvent.type(ui.filter.label.get(), 'severity=critical'); // Assert expect(ui.ruleList.row.queryAll()).toHaveLength(1); diff --git a/public/app/features/alerting/unified/AlertsFolderView.tsx b/public/app/features/alerting/unified/AlertsFolderView.tsx index 1109dc3c9c6..77124c6a01b 100644 --- a/public/app/features/alerting/unified/AlertsFolderView.tsx +++ b/public/app/features/alerting/unified/AlertsFolderView.tsx @@ -1,4 +1,9 @@ import { css } from '@emotion/css'; +import { isEqual, orderBy, uniqWith } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useDebounce } from 'react-use'; + import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; import { Card, FilterInput, Icon, Pagination, Select, TagList, useStyles2 } from '@grafana/ui'; @@ -6,10 +11,7 @@ import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants'; import { getQueryParamValue } from 'app/core/utils/query'; import { FolderState } from 'app/types'; import { CombinedRule } from 'app/types/unified-alerting'; -import { isEqual, orderBy, uniqWith } from 'lodash'; -import React, { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useDebounce } from 'react-use'; + import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces'; import { usePagination } from './hooks/usePagination'; import { useURLSearchParams } from './hooks/useURLSearchParams'; diff --git a/public/app/features/alerting/unified/AmRoutes.test.tsx b/public/app/features/alerting/unified/AmRoutes.test.tsx index 85a561165ba..1157269c68d 100644 --- a/public/app/features/alerting/unified/AmRoutes.test.tsx +++ b/public/app/features/alerting/unified/AmRoutes.test.tsx @@ -1,8 +1,13 @@ -import React from 'react'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; +import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'; + +import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { selectOptionInTest } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; import { AlertManagerCortexConfig, AlertManagerDataSourceJsonData, @@ -11,17 +16,14 @@ import { Route, } from 'app/plugins/datasource/alertmanager/types'; import { configureStore } from 'app/store/configureStore'; -import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'; +import { AccessControlAction } from 'app/types'; + import AmRoutes from './AmRoutes'; import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager'; import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks'; import { getAllDataSources } from './utils/config'; -import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; -import userEvent from '@testing-library/user-event'; -import { selectOptionInTest } from '@grafana/ui'; import { ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants'; -import { contextSrv } from 'app/core/services/context_srv'; -import { AccessControlAction } from 'app/types'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; jest.mock('./api/alertmanager'); jest.mock('./utils/config'); @@ -273,24 +275,24 @@ describe('AmRoutes', () => { // open root route for editing const rootRouteContainer = await ui.rootRouteContainer.find(); - userEvent.click(ui.editButton.get(rootRouteContainer)); + await userEvent.click(ui.editButton.get(rootRouteContainer)); // configure receiver & group by const receiverSelect = await ui.receiverSelect.find(); await clickSelectOption(receiverSelect, 'critical'); const groupSelect = ui.groupSelect.get(); - userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}'); + await userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}'); // configure timing intervals - userEvent.click(byText('Timing options').get(rootRouteContainer)); + await userEvent.click(byText('Timing options').get(rootRouteContainer)); await updateTiming(ui.groupWaitContainer.get(), '1', 'Minutes'); await updateTiming(ui.groupIntervalContainer.get(), '4', 'Minutes'); await updateTiming(ui.groupRepeatContainer.get(), '5', 'Hours'); //save - userEvent.click(ui.saveButton.get(rootRouteContainer)); + await userEvent.click(ui.saveButton.get(rootRouteContainer)); // wait for it to go out of edit mode await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument()); @@ -333,17 +335,17 @@ describe('AmRoutes', () => { // open root route for editing const rootRouteContainer = await ui.rootRouteContainer.find(); - userEvent.click(ui.editButton.get(rootRouteContainer)); + await userEvent.click(ui.editButton.get(rootRouteContainer)); // configure receiver & group by const receiverSelect = await ui.receiverSelect.find(); await clickSelectOption(receiverSelect, 'default'); const groupSelect = ui.groupSelect.get(); - userEvent.type(byRole('combobox').get(groupSelect), 'severity{enter}'); - userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}'); + await userEvent.type(byRole('combobox').get(groupSelect), 'severity{enter}'); + await userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}'); //save - userEvent.click(ui.saveButton.get(rootRouteContainer)); + await userEvent.click(ui.saveButton.get(rootRouteContainer)); // wait for it to go out of edit mode await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument()); @@ -426,8 +428,8 @@ describe('AmRoutes', () => { // Toggle a save to test new object_matchers const rootRouteContainer = await ui.rootRouteContainer.find(); - userEvent.click(ui.editButton.get(rootRouteContainer)); - userEvent.click(ui.saveButton.get(rootRouteContainer)); + await userEvent.click(ui.editButton.get(rootRouteContainer)); + await userEvent.click(ui.saveButton.get(rootRouteContainer)); await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument()); @@ -497,8 +499,8 @@ describe('AmRoutes', () => { // Toggle a save to test new object_matchers const rootRouteContainer = await ui.rootRouteContainer.find(); - userEvent.click(ui.editButton.get(rootRouteContainer)); - userEvent.click(ui.saveButton.get(rootRouteContainer)); + await userEvent.click(ui.editButton.get(rootRouteContainer)); + await userEvent.click(ui.saveButton.get(rootRouteContainer)); await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument()); @@ -599,7 +601,7 @@ describe('AmRoutes', () => { await renderAmRoutes(dataSources.am.name); const rows = await ui.row.findAll(); expect(rows).toHaveLength(1); - userEvent.click(ui.editRouteButton.get(rows[0])); + await userEvent.click(ui.editRouteButton.get(rows[0])); const muteTimingSelect = ui.muteTimingSelect.get(); await clickSelectOption(muteTimingSelect, 'default-mute'); @@ -608,7 +610,7 @@ describe('AmRoutes', () => { const savePolicyButton = ui.savePolicyButton.get(); expect(savePolicyButton).toBeInTheDocument(); - userEvent.click(savePolicyButton); + await userEvent.click(savePolicyButton); await waitFor(() => expect(savePolicyButton).not.toBeInTheDocument()); @@ -637,14 +639,14 @@ describe('AmRoutes', () => { }); const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise => { - userEvent.click(byRole('combobox').get(selectElement)); + await userEvent.click(byRole('combobox').get(selectElement)); await selectOptionInTest(selectElement, optionText); }; const updateTiming = async (selectElement: HTMLElement, value: string, timeUnit: string): Promise => { const input = byRole('textbox').get(selectElement); const select = byRole('combobox').get(selectElement); - userEvent.type(input, value); - userEvent.click(select); + await userEvent.type(input, value); + await userEvent.click(select); await selectOptionInTest(selectElement, timeUnit); }; diff --git a/public/app/features/alerting/unified/AmRoutes.tsx b/public/app/features/alerting/unified/AmRoutes.tsx index 8bcdaa7e184..489de5941dc 100644 --- a/public/app/features/alerting/unified/AmRoutes.tsx +++ b/public/app/features/alerting/unified/AmRoutes.tsx @@ -1,23 +1,26 @@ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Redirect } from 'react-router-dom'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; import { Receiver } from 'app/plugins/datasource/alertmanager/types'; + import { useCleanup } from '../../../core/hooks/useCleanup'; -import { AlertingPageWrapper } from './components/AlertingPageWrapper'; + import { AlertManagerPicker } from './components/AlertManagerPicker'; +import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { AmRootRoute } from './components/amroutes/AmRootRoute'; import { AmSpecificRouting } from './components/amroutes/AmSpecificRouting'; +import { MuteTimingsTable } from './components/amroutes/MuteTimingsTable'; import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; import { fetchAlertManagerConfigAction, updateAlertManagerConfigAction } from './state/actions'; import { AmRouteReceiver, FormAmRoute } from './types/amroutes'; import { amRouteToFormAmRoute, formAmRouteToAmRoute, stringsToSelectableValues } from './utils/amroutes'; -import { initialAsyncRequestState } from './utils/redux'; import { isVanillaPrometheusAlertManagerDataSource } from './utils/datasource'; -import { MuteTimingsTable } from './components/amroutes/MuteTimingsTable'; +import { initialAsyncRequestState } from './utils/redux'; const AmRoutes: FC = () => { const dispatch = useDispatch(); diff --git a/public/app/features/alerting/unified/MuteTimings.test.tsx b/public/app/features/alerting/unified/MuteTimings.test.tsx index d5248e923c8..7c7895ab757 100644 --- a/public/app/features/alerting/unified/MuteTimings.test.tsx +++ b/public/app/features/alerting/unified/MuteTimings.test.tsx @@ -1,18 +1,18 @@ -import React from 'react'; import { render, waitFor, fireEvent } from '@testing-library/react'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; - +import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { fetchAlertManagerConfig, updateAlertManagerConfig } from './api/alertmanager'; +import { byRole, byTestId, byText } from 'testing-library-selector'; + +import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; import { configureStore } from 'app/store/configureStore'; +import MuteTimings from './MuteTimings'; +import { fetchAlertManagerConfig, updateAlertManagerConfig } from './api/alertmanager'; import { mockDataSource, MockDataSourceSrv } from './mocks'; import { DataSourceType } from './utils/datasource'; -import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; -import { byRole, byTestId, byText } from 'testing-library-selector'; -import userEvent from '@testing-library/user-event'; -import MuteTimings from './MuteTimings'; jest.mock('./api/alertmanager'); @@ -118,11 +118,11 @@ describe('Mute timings', () => { await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled()); expect(ui.nameField.get()).toBeInTheDocument(); - userEvent.type(ui.nameField.get(), 'maintenance period'); - userEvent.type(ui.startsAt.get(), '22:00'); - userEvent.type(ui.endsAt.get(), '24:00'); - userEvent.type(ui.days.get(), '-1'); - userEvent.type(ui.months.get(), 'january, july'); + await userEvent.type(ui.nameField.get(), 'maintenance period'); + await userEvent.type(ui.startsAt.get(), '22:00'); + await userEvent.type(ui.endsAt.get(), '24:00'); + await userEvent.type(ui.days.get(), '-1'); + await userEvent.type(ui.months.get(), 'january, july'); fireEvent.submit(ui.form.get()); @@ -163,17 +163,17 @@ describe('Mute timings', () => { expect(ui.nameField.get()).toHaveValue(muteTimeInterval.name); expect(ui.months.get()).toHaveValue(muteTimeInterval.time_intervals[0].months?.join(', ')); - userEvent.clear(ui.startsAt.getAll()?.[0]); - userEvent.clear(ui.endsAt.getAll()?.[0]); - userEvent.clear(ui.weekdays.get()); - userEvent.clear(ui.days.get()); - userEvent.clear(ui.months.get()); - userEvent.clear(ui.years.get()); + await userEvent.clear(ui.startsAt.getAll()?.[0]); + await userEvent.clear(ui.endsAt.getAll()?.[0]); + await userEvent.clear(ui.weekdays.get()); + await userEvent.clear(ui.days.get()); + await userEvent.clear(ui.months.get()); + await userEvent.clear(ui.years.get()); - userEvent.type(ui.weekdays.get(), 'monday'); - userEvent.type(ui.days.get(), '-7:-1'); - userEvent.type(ui.months.get(), '3, 6, 9, 12'); - userEvent.type(ui.years.get(), '2021:2024'); + await userEvent.type(ui.weekdays.get(), 'monday'); + await userEvent.type(ui.days.get(), '-7:-1'); + await userEvent.type(ui.months.get(), '3, 6, 9, 12'); + await userEvent.type(ui.years.get(), '2021:2024'); fireEvent.submit(ui.form.get()); @@ -243,8 +243,8 @@ describe('Mute timings', () => { expect(ui.nameField.get()).toBeInTheDocument(); expect(ui.nameField.get()).toHaveValue(muteTimeInterval.name); - userEvent.clear(ui.nameField.get()); - userEvent.type(ui.nameField.get(), 'Lunch breaks'); + await userEvent.clear(ui.nameField.get()); + await userEvent.type(ui.nameField.get(), 'Lunch breaks'); fireEvent.submit(ui.form.get()); diff --git a/public/app/features/alerting/unified/MuteTimings.tsx b/public/app/features/alerting/unified/MuteTimings.tsx index 0087a3900d6..97221db287e 100644 --- a/public/app/features/alerting/unified/MuteTimings.tsx +++ b/public/app/features/alerting/unified/MuteTimings.tsx @@ -1,14 +1,16 @@ import React, { useCallback, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; import { Route, Redirect, Switch } from 'react-router-dom'; + +import { Alert, LoadingPlaceholder } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; + import MuteTimingForm from './components/amroutes/MuteTimingForm'; import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; -import { useDispatch } from 'react-redux'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; import { fetchAlertManagerConfigAction } from './state/actions'; import { initialAsyncRequestState } from './utils/redux'; -import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types'; -import { Alert, LoadingPlaceholder } from '@grafana/ui'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; const MuteTimings = () => { const [queryParams] = useQueryParams(); diff --git a/public/app/features/alerting/unified/PanelAlertTab.tsx b/public/app/features/alerting/unified/PanelAlertTab.tsx index cb45af4cd13..cbdf52eec11 100644 --- a/public/app/features/alerting/unified/PanelAlertTab.tsx +++ b/public/app/features/alerting/unified/PanelAlertTab.tsx @@ -1,6 +1,8 @@ +import React, { FC } from 'react'; + import { Tab, TabProps } from '@grafana/ui/src/components/Tabs/Tab'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; -import React, { FC } from 'react'; + import { usePanelCombinedRules } from './hooks/usePanelCombinedRules'; interface Props extends Omit { diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx index 659d9076f79..ce77abe5a0c 100644 --- a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx +++ b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx @@ -1,11 +1,21 @@ +import { render, act } from '@testing-library/react'; import React from 'react'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import { configureStore } from 'app/store/configureStore'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { render, act } from '@testing-library/react'; -import { PanelAlertTabContent } from './PanelAlertTabContent'; +import { byTestId } from 'testing-library-selector'; + +import { DataSourceApi } from '@grafana/data'; +import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { PrometheusDatasource } from 'app/plugins/datasource/prometheus/datasource'; +import { PromOptions } from 'app/plugins/datasource/prometheus/types'; +import { configureStore } from 'app/store/configureStore'; + +import { PanelAlertTabContent } from './PanelAlertTabContent'; +import { fetchRules } from './api/prometheus'; +import { fetchRulerRules } from './api/ruler'; import { mockDataSource, MockDataSourceSrv, @@ -14,17 +24,9 @@ import { mockPromRuleNamespace, mockRulerGrafanaRule, } from './mocks'; -import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { getAllDataSources } from './utils/config'; -import { fetchRules } from './api/prometheus'; -import { fetchRulerRules } from './api/ruler'; import { Annotation } from './utils/constants'; -import { byTestId } from 'testing-library-selector'; -import { PrometheusDatasource } from 'app/plugins/datasource/prometheus/datasource'; -import { DataSourceApi } from '@grafana/data'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { PromOptions } from 'app/plugins/datasource/prometheus/types'; -import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; jest.mock('./api/prometheus'); jest.mock('./api/ruler'); diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.tsx index 6832a62b200..a8b99dbc0de 100644 --- a/public/app/features/alerting/unified/PanelAlertTabContent.tsx +++ b/public/app/features/alerting/unified/PanelAlertTabContent.tsx @@ -1,14 +1,16 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Alert, CustomScrollbar, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; -import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import React, { FC } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Alert, CustomScrollbar, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; + import { NewRuleFromPanelButton } from './components/panel-alerts-tab/NewRuleFromPanelButton'; import { RulesTable } from './components/rules/RulesTable'; import { usePanelCombinedRules } from './hooks/usePanelCombinedRules'; -import { selectors } from '@grafana/e2e-selectors'; import { getRulesPermissions } from './utils/access-control'; -import { contextSrv } from 'app/core/services/context_srv'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx index 0b335b3c5f1..fc85f31a484 100644 --- a/public/app/features/alerting/unified/Receivers.test.tsx +++ b/public/app/features/alerting/unified/Receivers.test.tsx @@ -1,12 +1,22 @@ -import { configureStore } from 'app/store/configureStore'; +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import Receivers from './Receivers'; -import React from 'react'; +import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector'; + import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import { act, render, waitFor } from '@testing-library/react'; -import { getAllDataSources } from './utils/config'; +import { selectOptionInTest } from '@grafana/ui'; +import { interceptLinkClicks } from 'app/core/navigation/patch/interceptLinkClicks'; +import { contextSrv } from 'app/core/services/context_srv'; +import store from 'app/core/store'; +import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; +import { configureStore } from 'app/store/configureStore'; +import { AccessControlAction } from 'app/types'; + +import Receivers from './Receivers'; import { updateAlertManagerConfig, fetchAlertManagerConfig, fetchStatus, testReceivers } from './api/alertmanager'; +import { fetchNotifiers } from './api/grafana'; import { mockDataSource, MockDataSourceSrv, @@ -14,18 +24,10 @@ import { someCloudAlertManagerStatus, someGrafanaAlertManagerConfig, } from './mocks'; -import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; -import { fetchNotifiers } from './api/grafana'; import { grafanaNotifiersMock } from './mocks/grafana-notifiers'; -import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector'; -import userEvent from '@testing-library/user-event'; +import { getAllDataSources } from './utils/config'; import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants'; -import store from 'app/core/store'; -import { contextSrv } from 'app/core/services/context_srv'; -import { selectOptionInTest } from '@grafana/ui'; -import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; -import { interceptLinkClicks } from 'app/core/navigation/patch/interceptLinkClicks'; -import { AccessControlAction } from 'app/types'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; jest.mock('./api/alertmanager'); jest.mock('./api/grafana'); @@ -116,7 +118,7 @@ const ui = { }; const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise => { - userEvent.click(byRole('combobox').get(selectElement)); + await userEvent.click(byRole('combobox').get(selectElement)); await selectOptionInTest(selectElement, optionText); }; @@ -194,29 +196,25 @@ describe('Receivers', () => { await renderReceivers(); // go to new contact point page - await act(async () => { - userEvent.click(await ui.newContactPointButton.find()); - }); + await userEvent.click(await ui.newContactPointButton.find()); await byRole('heading', { name: /create contact point/i }).find(); expect(locationService.getLocation().pathname).toEqual('/alerting/notifications/receivers/new'); - await act(async () => { - // type in a name for the new receiver - userEvent.type(ui.inputs.name.get(), 'my new receiver'); + // type in a name for the new receiver + await userEvent.type(ui.inputs.name.get(), 'my new receiver'); - // enter some email - const email = ui.inputs.email.addresses.get(); - userEvent.clear(email); - userEvent.type(email, 'tester@grafana.com'); + // enter some email + const email = ui.inputs.email.addresses.get(); + await userEvent.clear(email); + await userEvent.type(email, 'tester@grafana.com'); - // try to test the contact point - userEvent.click(await ui.testContactPointButton.find()); - }); + // try to test the contact point + await userEvent.click(await ui.testContactPointButton.find()); await waitFor(() => expect(ui.testContactPointModal.get()).toBeInTheDocument(), { timeout: 1000 }); - userEvent.click(ui.customContactPointOption.get()); + await userEvent.click(ui.customContactPointOption.get()); await waitFor(() => expect(ui.contactPointAnnotationSelect(0).get()).toBeInTheDocument()); // enter custom annotations and labels @@ -224,7 +222,7 @@ describe('Receivers', () => { await userEvent.type(ui.contactPointAnnotationValue(0).get(), 'Test contact point'); await userEvent.type(ui.contactPointLabelKey(0).get(), 'foo'); await userEvent.type(ui.contactPointLabelValue(0).get(), 'bar'); - userEvent.click(ui.testContactPoint.get()); + await userEvent.click(ui.testContactPoint.get()); await waitFor(() => expect(mocks.api.testReceivers).toHaveBeenCalled()); @@ -260,7 +258,7 @@ describe('Receivers', () => { expect(locationService.getLocation().pathname).toEqual('/alerting/notifications/receivers/new'); // type in a name for the new receiver - userEvent.type(byPlaceholderText('Name').get(), 'my new receiver'); + await userEvent.type(byPlaceholderText('Name').get(), 'my new receiver'); // check that default email form is rendered await ui.inputs.email.addresses.find(); @@ -274,13 +272,10 @@ describe('Receivers', () => { const urlInput = ui.inputs.hipchat.url.get(); const apiKeyInput = ui.inputs.hipchat.apiKey.get(); - userEvent.type(urlInput, 'http://hipchat'); - userEvent.type(apiKeyInput, 'foobarbaz'); + await userEvent.type(urlInput, 'http://hipchat'); + await userEvent.type(apiKeyInput, 'foobarbaz'); - // it seems react-hook-form does some async state updates after submit - await act(async () => { - userEvent.click(await ui.saveContactButton.find()); - }); + await userEvent.click(await ui.saveContactButton.find()); // see that we're back to main page and proper api calls have been made await ui.receiversTable.find(); @@ -350,13 +345,13 @@ describe('Receivers', () => { // modify webhook url const slackContainer = ui.channelFormContainer.get(); await userEvent.click(byText('Optional Slack settings').get(slackContainer)); - userEvent.type(ui.inputs.slack.webhookURL.get(slackContainer), 'http://newgreaturl'); + await userEvent.type(ui.inputs.slack.webhookURL.get(slackContainer), 'http://newgreaturl'); // add confirm button to action await userEvent.click(byText(/Actions \(1\)/i).get(slackContainer)); await userEvent.click(await byTestId('items.1.settings.actions.0.confirm.add-button').find()); const confirmSubform = byTestId('items.1.settings.actions.0.confirm.container').get(); - userEvent.type(byLabelText('Text').get(confirmSubform), 'confirm this'); + await userEvent.type(byLabelText('Text').get(confirmSubform), 'confirm this'); // delete a field await userEvent.click(byText(/Fields \(2\)/i).get(slackContainer)); @@ -366,12 +361,9 @@ describe('Receivers', () => { // add another channel await userEvent.click(ui.newContactPointTypeButton.get()); await clickSelectOption(await byTestId('items.2.type').find(), 'Webhook'); - userEvent.type(await ui.inputs.webhook.URL.find(), 'http://webhookurl'); + await userEvent.type(await ui.inputs.webhook.URL.find(), 'http://webhookurl'); - // it seems react-hook-form does some async state updates after submit - await act(async () => { - await userEvent.click(ui.saveContactButton.get()); - }); + await userEvent.click(ui.saveContactButton.get()); // see that we're back to main page and proper api calls have been made await ui.receiversTable.find(); @@ -438,7 +430,7 @@ describe('Receivers', () => { const receiverRows = receiversTable.querySelectorAll('tbody tr'); expect(receiverRows[0]).toHaveTextContent('cloud-receiver'); expect(byTestId('edit').query(receiverRows[0])).not.toBeInTheDocument(); - userEvent.click(byTestId('view').get(receiverRows[0])); + await userEvent.click(byTestId('view').get(receiverRows[0])); // check that form is open await byRole('heading', { name: /contact point/i }).find(); diff --git a/public/app/features/alerting/unified/Receivers.tsx b/public/app/features/alerting/unified/Receivers.tsx index cf503117041..3335aa988ee 100644 --- a/public/app/features/alerting/unified/Receivers.tsx +++ b/public/app/features/alerting/unified/Receivers.tsx @@ -1,9 +1,11 @@ -import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui'; import React, { FC, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom'; -import { AlertingPageWrapper } from './components/AlertingPageWrapper'; + +import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui'; + import { AlertManagerPicker } from './components/AlertManagerPicker'; +import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { EditReceiverView } from './components/receivers/EditReceiverView'; import { EditTemplateView } from './components/receivers/EditTemplateView'; import { GlobalConfigForm } from './components/receivers/GlobalConfigForm'; diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx index cbd358bd935..dc6298d1dc2 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx @@ -1,14 +1,17 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; + import { DataSourceJsonData, PluginMeta } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { RedirectToRuleViewer } from './RedirectToRuleViewer'; import { configureStore } from 'app/store/configureStore'; -import { useCombinedRulesMatching } from './hooks/useCombinedRule'; + import { CombinedRule, Rule } from '../../../types/unified-alerting'; import { PromRuleType } from '../../../types/unified-alerting-dto'; + +import { RedirectToRuleViewer } from './RedirectToRuleViewer'; +import { useCombinedRulesMatching } from './hooks/useCombinedRule'; import { getRulesSourceByName } from './utils/datasource'; jest.mock('./hooks/useCombinedRule'); diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx index 51c6f21275e..c2290067538 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx @@ -1,14 +1,16 @@ +import { css } from '@emotion/css'; import React from 'react'; import { Redirect } from 'react-router-dom'; -import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Card, Icon, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { useCombinedRulesMatching } from './hooks/useCombinedRule'; -import { createViewLink } from './utils/misc'; -import { getRulesSourceByName } from './utils/datasource'; -import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout'; + import { AlertLabels } from './components/AlertLabels'; +import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout'; +import { useCombinedRulesMatching } from './hooks/useCombinedRule'; +import { getRulesSourceByName } from './utils/datasource'; +import { createViewLink } from './utils/misc'; type RedirectToRuleViewerProps = GrafanaRouteComponentProps<{ name?: string; sourceName?: string }>; const pageTitle = 'Alerting / Find rule'; diff --git a/public/app/features/alerting/unified/RuleEditor.test.tsx b/public/app/features/alerting/unified/RuleEditor.test.tsx index d257846c641..b3908041160 100644 --- a/public/app/features/alerting/unified/RuleEditor.test.tsx +++ b/public/app/features/alerting/unified/RuleEditor.test.tsx @@ -1,25 +1,28 @@ -import { Matcher, render, waitFor } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { BackendSrv, locationService, setBackendSrv, setDataSourceSrv } from '@grafana/runtime'; -import { configureStore } from 'app/store/configureStore'; -import RuleEditor from './RuleEditor'; -import { Route, Router } from 'react-router-dom'; +import { Matcher, render, waitFor, screen } from '@testing-library/react'; +import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; import React from 'react'; +import { Provider } from 'react-redux'; +import { Route, Router } from 'react-router-dom'; import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'; + +import { DataSourceInstanceSettings } from '@grafana/data'; +import { BackendSrv, locationService, setBackendSrv, setDataSourceSrv } from '@grafana/runtime'; import { selectOptionInTest } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; -import { mockDataSource, MockDataSourceSrv } from './mocks'; -import userEvent from '@testing-library/user-event'; -import { DataSourceInstanceSettings } from '@grafana/data'; -import { getAllDataSources } from './utils/config'; -import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; -import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; import { DashboardSearchHit } from 'app/features/search/types'; -import { getDefaultQueries } from './utils/rule-form'; -import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; -import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions'; +import { configureStore } from 'app/store/configureStore'; import { GrafanaAlertStateDecision, PromApplication } from 'app/types/unified-alerting-dto'; + +import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions'; + +import RuleEditor from './RuleEditor'; import { fetchBuildInfo } from './api/buildInfo'; +import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; +import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; +import { mockDataSource, MockDataSourceSrv } from './mocks'; +import { getAllDataSources } from './utils/config'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; +import { getDefaultQueries } from './utils/rule-form'; jest.mock('./components/rule-editor/ExpressionEditor', () => ({ // eslint-disable-next-line react/display-name @@ -144,30 +147,30 @@ describe('RuleEditor', () => { await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled()); - userEvent.type(await ui.inputs.name.find(), 'my great new rule'); - userEvent.click(await ui.buttons.lotexAlert.get()); + await userEvent.type(await ui.inputs.name.find(), 'my great new rule'); + await userEvent.click(await ui.buttons.lotexAlert.get()); const dataSourceSelect = ui.inputs.dataSource.get(); - userEvent.click(byRole('combobox').get(dataSourceSelect)); + await userEvent.click(byRole('combobox').get(dataSourceSelect)); await clickSelectOption(dataSourceSelect, 'Prom (default)'); await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled()); await clickSelectOption(ui.inputs.namespace.get(), 'namespace2'); await clickSelectOption(ui.inputs.group.get(), 'group2'); - userEvent.type(ui.inputs.expr.get(), 'up == 1'); + await userEvent.type(ui.inputs.expr.get(), 'up == 1'); - userEvent.type(ui.inputs.annotationValue(0).get(), 'some summary'); - userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); + await userEvent.type(ui.inputs.annotationValue(0).get(), 'some summary'); + await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.buttons.addLabel.get(), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - userEvent.type(ui.inputs.labelKey(0).get(), 'severity'); - userEvent.type(ui.inputs.labelValue(0).get(), 'warn'); - userEvent.type(ui.inputs.labelKey(1).get(), 'team'); - userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team'); + await userEvent.type(ui.inputs.labelKey(0).get(), 'severity'); + await userEvent.type(ui.inputs.labelValue(0).get(), 'warn'); + await userEvent.type(ui.inputs.labelKey(1).get(), 'team'); + await userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team'); // save and check what was sent to backend - userEvent.click(ui.buttons.save.get()); + await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( { dataSourceName: 'Prom', apiVersion: 'legacy' }, @@ -244,31 +247,34 @@ describe('RuleEditor', () => { await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled()); - userEvent.type(await ui.inputs.name.find(), 'my great new rule'); + await userEvent.type(await ui.inputs.name.find(), 'my great new rule'); const folderInput = await ui.inputs.folder.find(); await clickSelectOption(folderInput, 'Folder A'); - userEvent.type(ui.inputs.annotationValue(0).get(), 'some summary'); - userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); + const groupInput = screen.getByRole('textbox', { name: /^Group/ }); + await userEvent.type(groupInput, 'my group'); + + await userEvent.type(ui.inputs.annotationValue(0).get(), 'some summary'); + await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.buttons.addLabel.get(), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - userEvent.type(ui.inputs.labelKey(0).get(), 'severity'); - userEvent.type(ui.inputs.labelValue(0).get(), 'warn'); - userEvent.type(ui.inputs.labelKey(1).get(), 'team'); - userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team'); + await userEvent.type(ui.inputs.labelKey(0).get(), 'severity'); + await userEvent.type(ui.inputs.labelValue(0).get(), 'warn'); + await userEvent.type(ui.inputs.labelKey(1).get(), 'team'); + await userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team'); // save and check what was sent to backend - userEvent.click(ui.buttons.save.get()); + await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' }, 'Folder A', { interval: '1m', - name: 'my great new rule', + name: 'my group', rules: [ { annotations: { description: 'some description', summary: 'some summary' }, @@ -333,27 +339,27 @@ describe('RuleEditor', () => { await renderRuleEditor(); await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.fetchBuildInfo).toHaveBeenCalled()); - userEvent.type(await ui.inputs.name.find(), 'my great new recording rule'); - userEvent.click(await ui.buttons.lotexRecordingRule.get()); + await userEvent.type(await ui.inputs.name.find(), 'my great new recording rule'); + await userEvent.click(await ui.buttons.lotexRecordingRule.get()); const dataSourceSelect = ui.inputs.dataSource.get(); - userEvent.click(byRole('combobox').get(dataSourceSelect)); + await userEvent.click(byRole('combobox').get(dataSourceSelect)); await clickSelectOption(dataSourceSelect, 'Prom (default)'); await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled()); await clickSelectOption(ui.inputs.namespace.get(), 'namespace2'); await clickSelectOption(ui.inputs.group.get(), 'group2'); - userEvent.type(ui.inputs.expr.get(), 'up == 1'); + await userEvent.type(ui.inputs.expr.get(), 'up == 1'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.buttons.addLabel.get(), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - userEvent.type(ui.inputs.labelKey(1).get(), 'team'); - userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team'); + await userEvent.type(ui.inputs.labelKey(1).get(), 'team'); + await userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team'); // try to save, find out that recording rule name is invalid - userEvent.click(ui.buttons.save.get()); + await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect( byText( @@ -364,11 +370,11 @@ describe('RuleEditor', () => { expect(mocks.api.setRulerRuleGroup).not.toBeCalled(); // fix name and re-submit - userEvent.type(await ui.inputs.name.find(), '{selectall}{del}my:great:new:recording:rule'); - userEvent.click(ui.buttons.save.get()); + await userEvent.clear(await ui.inputs.name.find()); + await userEvent.type(await ui.inputs.name.find(), 'my:great:new:recording:rule'); // save and check what was sent to backend - userEvent.click(ui.buttons.save.get()); + await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( { dataSourceName: 'Prom', apiVersion: 'legacy' }, @@ -455,15 +461,15 @@ describe('RuleEditor', () => { // add an annotation await clickSelectOption(ui.inputs.annotationKey(2).get(), /Add new/); - userEvent.type(byRole('textbox').get(ui.inputs.annotationKey(2).get()), 'custom'); - userEvent.type(ui.inputs.annotationValue(2).get(), 'value'); + await userEvent.type(byRole('textbox').get(ui.inputs.annotationKey(2).get()), 'custom'); + await userEvent.type(ui.inputs.annotationValue(2).get(), 'value'); //add a label - userEvent.type(ui.inputs.labelKey(2).get(), 'custom'); - userEvent.type(ui.inputs.labelValue(2).get(), 'value'); + await userEvent.type(ui.inputs.labelKey(2).get(), 'custom'); + await userEvent.type(ui.inputs.labelValue(2).get(), 'value'); // save and check what was sent to backend - userEvent.click(ui.buttons.save.get()); + await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( @@ -611,11 +617,11 @@ describe('RuleEditor', () => { await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled()); await ui.inputs.name.find(); - userEvent.click(await ui.buttons.lotexAlert.get()); + await userEvent.click(await ui.buttons.lotexAlert.get()); // check that only rules sources that have ruler available are there const dataSourceSelect = ui.inputs.dataSource.get(); - userEvent.click(byRole('combobox').get(dataSourceSelect)); + await userEvent.click(byRole('combobox').get(dataSourceSelect)); expect(await byText('loki with ruler').query()).toBeInTheDocument(); expect(byText('cortex with ruler').query()).toBeInTheDocument(); expect(byText('loki with local rule store').query()).not.toBeInTheDocument(); @@ -626,6 +632,6 @@ describe('RuleEditor', () => { }); const clickSelectOption = async (selectElement: HTMLElement, optionText: Matcher): Promise => { - userEvent.click(byRole('combobox').get(selectElement)); + await userEvent.click(byRole('combobox').get(selectElement)); await selectOptionInTest(selectElement, optionText as string); }; diff --git a/public/app/features/alerting/unified/RuleEditor.tsx b/public/app/features/alerting/unified/RuleEditor.tsx index 143c1f597ab..3b29811a926 100644 --- a/public/app/features/alerting/unified/RuleEditor.tsx +++ b/public/app/features/alerting/unified/RuleEditor.tsx @@ -1,18 +1,20 @@ import { css } from '@emotion/css'; +import React, { FC, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { useAsync } from 'react-use'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, LinkButton, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { contextSrv } from 'app/core/services/context_srv'; import { RuleIdentifier } from 'app/types/unified-alerting'; -import React, { FC, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; -import { useAsync } from 'react-use'; + import { AlertRuleForm } from './components/rule-editor/AlertRuleForm'; import { useIsRuleEditable } from './hooks/useIsRuleEditable'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; import { fetchAllPromBuildInfoAction, fetchEditableRuleAction } from './state/actions'; +import { useRulesAccess } from './utils/accessControlHooks'; import * as ruleId from './utils/rule-id'; interface ExistingRuleEditorProps { @@ -38,6 +40,7 @@ const ExistingRuleEditor: FC = ({ identifier }) => { ); } + if (error) { return ( @@ -47,12 +50,15 @@ const ExistingRuleEditor: FC = ({ identifier }) => { ); } + if (!result) { return Sorry! This rule does not exist.; } + if (isEditable === false) { return Sorry! You do not have permission to edit this rule.; } + return ; }; @@ -67,10 +73,16 @@ const RuleEditor: FC = ({ match }) => { await dispatch(fetchAllPromBuildInfoAction()); }, [dispatch]); - if (!(contextSrv.hasEditPermissionInFolders || contextSrv.isEditor)) { + const { canCreateGrafanaRules, canCreateCloudRules, canEditRules } = useRulesAccess(); + + if (!canCreateGrafanaRules && !canCreateCloudRules) { return Sorry! You are not allowed to create rules.; } + if (identifier && !canEditRules(identifier.ruleSourceName)) { + return Sorry! You are not allowed to edit rules.; + } + if (loading) { return ( diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 1e55be07468..2b3bcb17330 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -1,12 +1,18 @@ -import React from 'react'; +import { SerializedError } from '@reduxjs/toolkit'; import { render, waitFor } from '@testing-library/react'; -import { configureStore } from 'app/store/configureStore'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; -import RuleList from './RuleList'; +import { Router } from 'react-router-dom'; import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'; -import { getAllDataSources } from './utils/config'; -import { fetchRules } from './api/prometheus'; + +import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { configureStore } from 'app/store/configureStore'; +import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto'; + +import RuleList from './RuleList'; import { fetchBuildInfo } from './api/buildInfo'; +import { fetchRules } from './api/prometheus'; import { fetchRulerRules, deleteRulerRulesGroup, deleteNamespace, setRulerRuleGroup } from './api/ruler'; import { mockDataSource, @@ -19,12 +25,8 @@ import { somePromRules, someRulerRules, } from './mocks'; +import { getAllDataSources } from './utils/config'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; -import { SerializedError } from '@reduxjs/toolkit'; -import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto'; -import userEvent from '@testing-library/user-event'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import { Router } from 'react-router-dom'; jest.mock('./api/buildInfo'); jest.mock('./api/prometheus'); @@ -192,17 +194,17 @@ describe('RuleList', () => { expect(groups).toHaveLength(5); expect(groups[0]).toHaveTextContent('foofolder'); - expect(groups[1]).toHaveTextContent('default > group-1'); - expect(groups[2]).toHaveTextContent('default > group-1'); - expect(groups[3]).toHaveTextContent('default > group-2'); - expect(groups[4]).toHaveTextContent('lokins > group-1'); + expect(groups[1]).toHaveTextContent('default group-1'); + expect(groups[2]).toHaveTextContent('default group-1'); + expect(groups[3]).toHaveTextContent('default group-2'); + expect(groups[4]).toHaveTextContent('lokins group-1'); const errors = await ui.cloudRulesSourceErrors.find(); expect(errors).not.toHaveTextContent( 'Failed to load rules state from Prometheus-broken: this datasource is broken' ); - userEvent.click(ui.moreErrorsButton.get()); + await userEvent.click(ui.moreErrorsButton.get()); expect(errors).toHaveTextContent('Failed to load rules state from Prometheus-broken: this datasource is broken'); }); @@ -293,7 +295,7 @@ describe('RuleList', () => { // expand second group to see rules table expect(ui.rulesTable.query()).not.toBeInTheDocument(); - userEvent.click(ui.groupCollapseToggle.get(groups[1])); + await userEvent.click(ui.groupCollapseToggle.get(groups[1])); const table = await ui.rulesTable.find(groups[1]); // check that rule rows are rendered properly @@ -315,7 +317,7 @@ describe('RuleList', () => { expect(byText('Labels').query()).not.toBeInTheDocument(); // expand alert details - userEvent.click(ui.ruleCollapseToggle.get(ruleRows[1])); + await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[1])); const ruleDetails = ui.expandedContent.get(ruleRows[1]); @@ -334,17 +336,17 @@ describe('RuleList', () => { expect(instanceRows![1]).toHaveTextContent('Firingfoo=bazseverity=error2021-03-18 13:47:05'); // expand details of an instance - userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); + await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); const alertDetails = byTestId('expanded-content').get(instanceRows[0]); expect(alertDetails).toHaveTextContent('Value2e+10'); expect(alertDetails).toHaveTextContent('messagefirst alert message'); // collapse everything again - userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); + await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); expect(byTestId('expanded-content').query(instanceRows[0])).not.toBeInTheDocument(); - userEvent.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]); - userEvent.click(ui.groupCollapseToggle.get(groups[1])); + await userEvent.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]); + await userEvent.click(ui.groupCollapseToggle.get(groups[1])); expect(ui.rulesTable.query()).not.toBeInTheDocument(); }); @@ -456,33 +458,36 @@ describe('RuleList', () => { expect(groups).toHaveLength(2); const filterInput = ui.rulesFilterInput.get(); - userEvent.type(filterInput, '{{foo="bar"}'); + await userEvent.type(filterInput, '{{foo="bar"}'); // Input is debounced so wait for it to be visible await waitFor(() => expect(filterInput).toHaveValue('{foo="bar"}')); // Group doesn't contain matching labels await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1)); - userEvent.click(ui.groupCollapseToggle.get(groups[0])); + await userEvent.click(ui.groupCollapseToggle.get(groups[0])); const ruleRows = ui.ruleRow.getAll(groups[0]); expect(ruleRows).toHaveLength(1); - userEvent.click(ui.ruleCollapseToggle.get(ruleRows[0])); + await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[0])); const ruleDetails = ui.expandedContent.get(ruleRows[0]); expect(ruleDetails).toHaveTextContent('Labelsseverity=warningfoo=bar'); // Check for different label matchers - userEvent.type(filterInput, '{selectall}{del}{{foo!="bar",foo!="baz"}'); + await userEvent.clear(filterInput); + await userEvent.type(filterInput, '{{foo!="bar",foo!="baz"}'); // Group doesn't contain matching labels await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1)); await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2')); - userEvent.type(filterInput, '{selectall}{del}{{foo=~"b.+"}'); + await userEvent.clear(filterInput); + await userEvent.type(filterInput, '{{foo=~"b.+"}'); await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(2)); - userEvent.type(filterInput, '{selectall}{del}{{region="US"}'); + await userEvent.clear(filterInput); + await userEvent.type(filterInput, '{{region="US"}'); await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1)); await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2')); }); @@ -521,7 +526,7 @@ describe('RuleList', () => { expect(groups).toHaveLength(3); // open edit dialog - userEvent.click(ui.editCloudGroupIcon.get(groups[0])); + await userEvent.click(ui.editCloudGroupIcon.get(groups[0])); expect(ui.editGroupModal.namespaceInput.get()).toHaveValue('namespace1'); expect(ui.editGroupModal.ruleGroupInput.get()).toHaveValue('group1'); @@ -531,16 +536,16 @@ describe('RuleList', () => { testCase('rename both lotex namespace and group', async () => { // make changes to form - userEvent.clear(ui.editGroupModal.namespaceInput.get()); - userEvent.type(ui.editGroupModal.namespaceInput.get(), 'super namespace'); + await userEvent.clear(ui.editGroupModal.namespaceInput.get()); + await userEvent.type(ui.editGroupModal.namespaceInput.get(), 'super namespace'); - userEvent.clear(ui.editGroupModal.ruleGroupInput.get()); - userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); + await userEvent.clear(ui.editGroupModal.ruleGroupInput.get()); + await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); - userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); + await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); // submit, check that appropriate calls were made - userEvent.click(ui.editGroupModal.saveButton.get()); + await userEvent.click(ui.editGroupModal.saveButton.get()); await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); @@ -572,12 +577,12 @@ describe('RuleList', () => { testCase('rename just the lotex group', async () => { // make changes to form - userEvent.clear(ui.editGroupModal.ruleGroupInput.get()); - userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); - userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); + await userEvent.clear(ui.editGroupModal.ruleGroupInput.get()); + await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); + await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); // submit, check that appropriate calls were made - userEvent.click(ui.editGroupModal.saveButton.get()); + await userEvent.click(ui.editGroupModal.saveButton.get()); await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); @@ -604,10 +609,10 @@ describe('RuleList', () => { testCase('edit lotex group eval interval, no renaming', async () => { // make changes to form - userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); + await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); // submit, check that appropriate calls were made - userEvent.click(ui.editGroupModal.saveButton.get()); + await userEvent.click(ui.editGroupModal.saveButton.get()); await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx index a2ca8e82e2f..cb299fdfc3e 100644 --- a/public/app/features/alerting/unified/RuleList.tsx +++ b/public/app/features/alerting/unified/RuleList.tsx @@ -1,24 +1,26 @@ -import { GrafanaTheme2, urlUtil } from '@grafana/data'; -import { useStyles2, LinkButton, withErrorBoundary, Button } from '@grafana/ui'; +import { css } from '@emotion/css'; import React, { useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { useLocation } from 'react-router-dom'; + +import { GrafanaTheme2, urlUtil } from '@grafana/data'; +import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; + import { AlertingPageWrapper } from './components/AlertingPageWrapper'; import { NoRulesSplash } from './components/rules/NoRulesCTA'; -import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; -import { useFilteredRules } from './hooks/useFilteredRules'; -import { fetchAllPromAndRulerRulesAction } from './state/actions'; -import { getAllRulesSourceNames } from './utils/datasource'; -import { css } from '@emotion/css'; -import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces'; -import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants'; -import RulesFilter from './components/rules/RulesFilter'; +import { RuleListErrors } from './components/rules/RuleListErrors'; import { RuleListGroupView } from './components/rules/RuleListGroupView'; import { RuleListStateView } from './components/rules/RuleListStateView'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { useLocation } from 'react-router-dom'; -import { contextSrv } from 'app/core/services/context_srv'; import { RuleStats } from './components/rules/RuleStats'; -import { RuleListErrors } from './components/rules/RuleListErrors'; +import RulesFilter from './components/rules/RulesFilter'; +import { useCombinedRuleNamespaces } from './hooks/useCombinedRuleNamespaces'; +import { useFilteredRules } from './hooks/useFilteredRules'; +import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; +import { fetchAllPromAndRulerRulesAction } from './state/actions'; +import { useRulesAccess } from './utils/accessControlHooks'; +import { RULE_LIST_POLL_INTERVAL_MS } from './utils/constants'; +import { getAllRulesSourceNames } from './utils/datasource'; import { getFiltersFromUrlParams } from './utils/misc'; const VIEWS = { @@ -38,6 +40,8 @@ const RuleList = withErrorBoundary( const filters = getFiltersFromUrlParams(queryParams); const filtersActive = Object.values(filters).some((filter) => filter !== undefined); + const { canCreateGrafanaRules, canCreateCloudRules } = useRulesAccess(); + const view = VIEWS[queryParams['view'] as keyof typeof VIEWS] ? (queryParams['view'] as keyof typeof VIEWS) : 'groups'; @@ -93,7 +97,7 @@ const RuleList = withErrorBoundary( )}
- {(contextSrv.hasEditPermissionInFolders || contextSrv.isEditor) && ( + {(canCreateGrafanaRules || canCreateCloudRules) && ( ({ ...(jest.requireActual('@grafana/runtime') as any), diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 105e078bc4a..647795709f7 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -1,4 +1,7 @@ import { css } from '@emotion/css'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useObservable } from 'react-use'; + import { GrafanaTheme2, LoadingState, PanelData } from '@grafana/data'; import { Alert, @@ -11,9 +14,9 @@ import { withErrorBoundary, } from '@grafana/ui'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { useObservable } from 'react-use'; + import { AlertQuery } from '../../../types/unified-alerting-dto'; + import { AlertLabels } from './components/AlertLabels'; import { DetailsField } from './components/DetailsField'; import { RuleViewerLayout, RuleViewerLayoutContent } from './components/rule-viewer/RuleViewerLayout'; diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 13d91f7449e..d3f1fe9b1a5 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -1,21 +1,23 @@ -import React from 'react'; import { render, waitFor } from '@testing-library/react'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import { dateTime } from '@grafana/data'; +import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { fetchSilences, fetchAlerts, createOrUpdateSilence } from './api/alertmanager'; -import { configureStore } from 'app/store/configureStore'; -import Silences from './Silences'; -import { mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv, mockSilence } from './mocks'; -import { DataSourceType } from './utils/datasource'; -import { parseMatchers } from './utils/alertmanager'; -import { AlertState, MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector'; -import userEvent from '@testing-library/user-event'; + +import { dateTime } from '@grafana/data'; +import { locationService, setDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; +import { AlertState, MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; +import { configureStore } from 'app/store/configureStore'; import { AccessControlAction } from 'app/types'; +import Silences from './Silences'; +import { fetchSilences, fetchAlerts, createOrUpdateSilence } from './api/alertmanager'; +import { mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv, mockSilence } from './mocks'; +import { parseMatchers } from './utils/alertmanager'; +import { DataSourceType } from './utils/datasource'; + jest.mock('./api/alertmanager'); jest.mock('app/core/services/context_srv'); @@ -171,7 +173,8 @@ describe('Silences', () => { await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); const queryBar = ui.queryBar.get(); - userEvent.paste(queryBar, 'foo=bar'); + await userEvent.click(queryBar); + await userEvent.paste('foo=bar'); await waitFor(() => expect(ui.silenceRow.getAll()).toHaveLength(1)); }, @@ -254,40 +257,40 @@ describe('Silence edit', () => { const startDateString = dateTime(start).format('YYYY-MM-DD'); const endDateString = dateTime(end).format('YYYY-MM-DD'); - userEvent.clear(ui.editor.durationInput.get()); - userEvent.type(ui.editor.durationInput.get(), '1d'); + await userEvent.clear(ui.editor.durationInput.get()); + await userEvent.type(ui.editor.durationInput.get(), '1d'); await waitFor(() => expect(ui.editor.durationInput.query()).toHaveValue('1d')); await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(startDateString)); await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(endDateString)); - userEvent.type(ui.editor.matcherName.get(), 'foo'); - userEvent.type(ui.editor.matcherOperatorSelect.get(), '='); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.get(), 'bar'); + await userEvent.type(ui.editor.matcherName.get(), 'foo'); + await userEvent.type(ui.editor.matcherOperatorSelect.get(), '='); + await userEvent.tab(); + await userEvent.type(ui.editor.matcherValue.get(), 'bar'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); - userEvent.type(ui.editor.matcherName.getAll()[1], 'bar'); - userEvent.type(ui.editor.matcherOperatorSelect.getAll()[1], '!='); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.getAll()[1], 'buzz'); + await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.type(ui.editor.matcherName.getAll()[1], 'bar'); + await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[1], '!='); + await userEvent.tab(); + await userEvent.type(ui.editor.matcherValue.getAll()[1], 'buzz'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); - userEvent.type(ui.editor.matcherName.getAll()[2], 'region'); - userEvent.type(ui.editor.matcherOperatorSelect.getAll()[2], '=~'); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.getAll()[2], 'us-west-.*'); + await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.type(ui.editor.matcherName.getAll()[2], 'region'); + await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[2], '=~'); + await userEvent.tab(); + await userEvent.type(ui.editor.matcherValue.getAll()[2], 'us-west-.*'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(ui.editor.addMatcherButton.get(), undefined, { skipPointerEventsCheck: true }); - userEvent.type(ui.editor.matcherName.getAll()[3], 'env'); - userEvent.type(ui.editor.matcherOperatorSelect.getAll()[3], '!~'); - userEvent.tab(); - userEvent.type(ui.editor.matcherValue.getAll()[3], 'dev|staging'); + await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); + await userEvent.type(ui.editor.matcherName.getAll()[3], 'env'); + await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[3], '!~'); + await userEvent.tab(); + await userEvent.type(ui.editor.matcherValue.getAll()[3], 'dev|staging'); - userEvent.click(ui.editor.submit.get()); + await userEvent.click(ui.editor.submit.get()); await waitFor(() => expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( diff --git a/public/app/features/alerting/unified/Silences.tsx b/public/app/features/alerting/unified/Silences.tsx index 475e919c3f8..828a26a23df 100644 --- a/public/app/features/alerting/unified/Silences.tsx +++ b/public/app/features/alerting/unified/Silences.tsx @@ -1,20 +1,21 @@ import React, { FC, useEffect, useCallback } from 'react'; -import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui'; - import { useDispatch } from 'react-redux'; import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom'; + +import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui'; +import { Silence } from 'app/plugins/datasource/alertmanager/types'; +import { AccessControlAction } from 'app/types'; + +import { AlertManagerPicker } from './components/AlertManagerPicker'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; +import { Authorize } from './components/Authorize'; +import SilencesEditor from './components/silences/SilencesEditor'; import SilencesTable from './components/silences/SilencesTable'; import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; import { fetchAmAlertsAction, fetchSilencesAction } from './state/actions'; import { SILENCES_POLL_INTERVAL_MS } from './utils/constants'; import { AsyncRequestState, initialAsyncRequestState } from './utils/redux'; -import SilencesEditor from './components/silences/SilencesEditor'; -import { AlertManagerPicker } from './components/AlertManagerPicker'; -import { Silence } from 'app/plugins/datasource/alertmanager/types'; -import { AccessControlAction } from 'app/types'; -import { Authorize } from './components/Authorize'; const Silences: FC = () => { const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(); diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 9986590701c..fe73d3fd655 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -1,3 +1,5 @@ +import { lastValueFrom } from 'rxjs'; + import { urlUtil } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { @@ -15,9 +17,9 @@ import { TestReceiversResult, ExternalAlertmanagerConfig, } from 'app/plugins/datasource/alertmanager/types'; -import { lastValueFrom } from 'rxjs'; -import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; + import { isFetchError } from '../utils/alertmanager'; +import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; // "grafana" for grafana-managed, otherwise a datasource name export async function fetchAlertManagerConfig(alertManagerSourceName: string): Promise { diff --git a/public/app/features/alerting/unified/api/annotations.test.ts b/public/app/features/alerting/unified/api/annotations.test.ts index 183c103f58c..ae8cece3be5 100644 --- a/public/app/features/alerting/unified/api/annotations.test.ts +++ b/public/app/features/alerting/unified/api/annotations.test.ts @@ -1,7 +1,8 @@ import '@grafana/runtime'; -import { fetchAnnotations, sortStateHistory } from './annotations'; import { StateHistoryItem } from 'app/types/unified-alerting'; +import { fetchAnnotations, sortStateHistory } from './annotations'; + const get = jest.fn(() => { return new Promise((resolve) => { resolve(undefined); diff --git a/public/app/features/alerting/unified/api/buildInfo.test.ts b/public/app/features/alerting/unified/api/buildInfo.test.ts index 1564d7a19f6..48ea249e8bc 100644 --- a/public/app/features/alerting/unified/api/buildInfo.test.ts +++ b/public/app/features/alerting/unified/api/buildInfo.test.ts @@ -1,5 +1,7 @@ -import { PromApplication } from 'app/types/unified-alerting-dto'; import { of, throwError } from 'rxjs'; + +import { PromApplication } from 'app/types/unified-alerting-dto'; + import { fetchDataSourceBuildInfo } from './buildInfo'; import { fetchRules } from './prometheus'; import { fetchTestRulerRulesGroup } from './ruler'; @@ -8,6 +10,7 @@ const fetch = jest.fn(); jest.mock('./prometheus'); jest.mock('./ruler'); +jest.mock('app/core/services/context_srv', () => {}); jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ fetch }), })); diff --git a/public/app/features/alerting/unified/api/buildInfo.ts b/public/app/features/alerting/unified/api/buildInfo.ts index 465908f6a95..39ef33d5940 100644 --- a/public/app/features/alerting/unified/api/buildInfo.ts +++ b/public/app/features/alerting/unified/api/buildInfo.ts @@ -1,9 +1,12 @@ +import { lastValueFrom } from 'rxjs'; + import { getBackendSrv } from '@grafana/runtime'; import { PromApplication, PromBuildInfo, PromBuildInfoResponse } from 'app/types/unified-alerting-dto'; -import { lastValueFrom } from 'rxjs'; + import { isFetchError } from '../utils/alertmanager'; import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants'; import { getDataSourceByName } from '../utils/datasource'; + import { fetchRules } from './prometheus'; import { fetchTestRulerRulesGroup } from './ruler'; diff --git a/public/app/features/alerting/unified/api/preview.ts b/public/app/features/alerting/unified/api/preview.ts index 2d51276fced..20ba91453b9 100644 --- a/public/app/features/alerting/unified/api/preview.ts +++ b/public/app/features/alerting/unified/api/preview.ts @@ -1,3 +1,6 @@ +import { Observable, of } from 'rxjs'; +import { catchError, map, share } from 'rxjs/operators'; + import { dataFrameFromJSON, DataFrameJSON, @@ -7,8 +10,7 @@ import { withLoadingIndicator, } from '@grafana/data'; import { getBackendSrv, toDataQueryError } from '@grafana/runtime'; -import { Observable, of } from 'rxjs'; -import { catchError, map, share } from 'rxjs/operators'; + import { CloudPreviewRuleRequest, GrafanaPreviewRuleRequest, diff --git a/public/app/features/alerting/unified/api/prometheus.ts b/public/app/features/alerting/unified/api/prometheus.ts index db3598f04bc..acb42bd3678 100644 --- a/public/app/features/alerting/unified/api/prometheus.ts +++ b/public/app/features/alerting/unified/api/prometheus.ts @@ -1,8 +1,9 @@ import { lastValueFrom } from 'rxjs'; -import { getBackendSrv } from '@grafana/runtime'; +import { getBackendSrv } from '@grafana/runtime'; import { RuleNamespace } from 'app/types/unified-alerting'; import { PromRulesResponse } from 'app/types/unified-alerting-dto'; + import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; export interface FetchPromRulesFilter { diff --git a/public/app/features/alerting/unified/api/ruler.test.ts b/public/app/features/alerting/unified/api/ruler.test.ts index 1e1ec9ed62c..2713d0e975b 100644 --- a/public/app/features/alerting/unified/api/ruler.test.ts +++ b/public/app/features/alerting/unified/api/ruler.test.ts @@ -1,5 +1,7 @@ import { RulerDataSourceConfig } from 'app/types/unified-alerting'; + import { getDatasourceAPIId } from '../utils/datasource'; + import { rulerUrlBuilder } from './ruler'; jest.mock('../utils/datasource'); diff --git a/public/app/features/alerting/unified/api/ruler.ts b/public/app/features/alerting/unified/api/ruler.ts index c5c82f6317e..51af85a77e1 100644 --- a/public/app/features/alerting/unified/api/ruler.ts +++ b/public/app/features/alerting/unified/api/ruler.ts @@ -1,10 +1,12 @@ import { lastValueFrom } from 'rxjs'; -import { FetchResponse, getBackendSrv } from '@grafana/runtime'; -import { PostableRulerRuleGroupDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; -import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; -import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants'; +import { FetchResponse, getBackendSrv } from '@grafana/runtime'; import { RulerDataSourceConfig } from 'app/types/unified-alerting'; +import { PostableRulerRuleGroupDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; + +import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants'; +import { getDatasourceAPIId, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; + import { prepareRulesFilterQueryParams } from './prometheus'; interface ErrorResponseMessage { diff --git a/public/app/features/alerting/unified/components/AlertLabel.tsx b/public/app/features/alerting/unified/components/AlertLabel.tsx index 984e57a3492..5f77b5cf126 100644 --- a/public/app/features/alerting/unified/components/AlertLabel.tsx +++ b/public/app/features/alerting/unified/components/AlertLabel.tsx @@ -1,7 +1,8 @@ -import React, { FC } from 'react'; -import { IconButton, useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { IconButton, useStyles } from '@grafana/ui'; interface Props { labelKey: string; diff --git a/public/app/features/alerting/unified/components/AlertLabels.tsx b/public/app/features/alerting/unified/components/AlertLabels.tsx index 4397e56fd9e..e6fda61b93a 100644 --- a/public/app/features/alerting/unified/components/AlertLabels.tsx +++ b/public/app/features/alerting/unified/components/AlertLabels.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { TagList } from '@grafana/ui'; type Props = { labels: Record; className?: string }; diff --git a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx index e0107dbc575..19302227d16 100644 --- a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx +++ b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx @@ -1,9 +1,11 @@ -import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; -import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; -import React, { FC, useMemo } from 'react'; -import { Field, Select, useStyles2 } from '@grafana/ui'; -import { getAllDataSources } from '../utils/config'; import { css } from '@emotion/css'; +import React, { FC, useMemo } from 'react'; + +import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; +import { Field, Select, useStyles2 } from '@grafana/ui'; + +import { getAllDataSources } from '../utils/config'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; interface Props { onChange: (alertManagerSourceName: string) => void; diff --git a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx index db50e173e1d..21a7826139e 100644 --- a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx +++ b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx @@ -1,7 +1,8 @@ import React, { FC } from 'react'; +import { useSelector } from 'react-redux'; + import Page from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; -import { useSelector } from 'react-redux'; import { StoreState } from 'app/types/store'; interface Props { diff --git a/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx b/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx index ed27208dec9..5d8061c220c 100644 --- a/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx +++ b/public/app/features/alerting/unified/components/AnnotationDetailsField.tsx @@ -1,11 +1,14 @@ -import React, { FC } from 'react'; -import { Well } from './Well'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; import { Tooltip, useStyles } from '@grafana/ui'; -import { DetailsField } from './DetailsField'; + import { Annotation, annotationLabels } from '../utils/constants'; +import { DetailsField } from './DetailsField'; +import { Well } from './Well'; + const wellableAnnotationKeys = ['message', 'description']; interface Props { diff --git a/public/app/features/alerting/unified/components/Authorize.tsx b/public/app/features/alerting/unified/components/Authorize.tsx index d283a0454e8..20506481e89 100644 --- a/public/app/features/alerting/unified/components/Authorize.tsx +++ b/public/app/features/alerting/unified/components/Authorize.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; -import { AccessControlAction } from 'app/types'; + import { contextSrv } from 'app/core/services/context_srv'; +import { AccessControlAction } from 'app/types'; type Props = { actions: AccessControlAction[]; diff --git a/public/app/features/alerting/unified/components/CollapseToggle.test.tsx b/public/app/features/alerting/unified/components/CollapseToggle.test.tsx index 384da5879fd..9bdb1cf191b 100644 --- a/public/app/features/alerting/unified/components/CollapseToggle.test.tsx +++ b/public/app/features/alerting/unified/components/CollapseToggle.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from '@testing-library/react'; import { noop } from 'lodash'; import React from 'react'; + import { CollapseToggle } from './CollapseToggle'; describe('TestToggle', () => { diff --git a/public/app/features/alerting/unified/components/CollapseToggle.tsx b/public/app/features/alerting/unified/components/CollapseToggle.tsx index 3e670a396be..d5ae0433b00 100644 --- a/public/app/features/alerting/unified/components/CollapseToggle.tsx +++ b/public/app/features/alerting/unified/components/CollapseToggle.tsx @@ -1,5 +1,6 @@ -import React, { FC, HTMLAttributes } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, HTMLAttributes } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { IconSize, useStyles2, Button } from '@grafana/ui'; diff --git a/public/app/features/alerting/unified/components/DetailsField.tsx b/public/app/features/alerting/unified/components/DetailsField.tsx index 50c06c29724..28359475592 100644 --- a/public/app/features/alerting/unified/components/DetailsField.tsx +++ b/public/app/features/alerting/unified/components/DetailsField.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/alerting/unified/components/DynamicTable.tsx b/public/app/features/alerting/unified/components/DynamicTable.tsx index 5ff067820a2..9c5e0b4544c 100644 --- a/public/app/features/alerting/unified/components/DynamicTable.tsx +++ b/public/app/features/alerting/unified/components/DynamicTable.tsx @@ -1,5 +1,6 @@ -import React, { ReactNode, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { ReactNode, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { IconButton, useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx b/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx index 65f98b1524b..ad75ae1a53b 100644 --- a/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx +++ b/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx @@ -1,7 +1,9 @@ import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import React from 'react'; + import { DynamicTable, DynamicTableProps } from './DynamicTable'; export type DynamicTableWithGuidelinesProps = Omit, 'renderPrefixHeader, renderPrefixCell'>; diff --git a/public/app/features/alerting/unified/components/EmptyArea.tsx b/public/app/features/alerting/unified/components/EmptyArea.tsx index 6482547d906..d0129033d24 100644 --- a/public/app/features/alerting/unified/components/EmptyArea.tsx +++ b/public/app/features/alerting/unified/components/EmptyArea.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { useStyles } from '@grafana/ui'; diff --git a/public/app/features/alerting/unified/components/EmptyAreaWithCTA.tsx b/public/app/features/alerting/unified/components/EmptyAreaWithCTA.tsx index a1c8d834104..a0dc2bbf1fe 100644 --- a/public/app/features/alerting/unified/components/EmptyAreaWithCTA.tsx +++ b/public/app/features/alerting/unified/components/EmptyAreaWithCTA.tsx @@ -1,7 +1,9 @@ -import React, { ButtonHTMLAttributes, FC } from 'react'; import { css } from '@emotion/css'; +import React, { ButtonHTMLAttributes, FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { Button, ButtonVariant, IconName, LinkButton, useStyles } from '@grafana/ui'; + import { EmptyArea } from './EmptyArea'; export interface EmptyAreaWithCTAProps { diff --git a/public/app/features/alerting/unified/components/Expression.tsx b/public/app/features/alerting/unified/components/Expression.tsx index f04683fc7a5..239ea13043d 100644 --- a/public/app/features/alerting/unified/components/Expression.tsx +++ b/public/app/features/alerting/unified/components/Expression.tsx @@ -1,13 +1,16 @@ -import { Editor } from '@grafana/slate-react'; -import React, { FC, useMemo } from 'react'; -import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; -import LogqlSyntax from 'app/plugins/datasource/loki/syntax'; -import { LanguageMap, languages as prismLanguages } from 'prismjs'; -import { makeValue, SlatePrism, useStyles } from '@grafana/ui'; import { css, cx } from '@emotion/css'; +import { LanguageMap, languages as prismLanguages } from 'prismjs'; +import React, { FC, useMemo } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { Editor } from '@grafana/slate-react'; +import { makeValue, SlatePrism, useStyles } from '@grafana/ui'; +import LogqlSyntax from 'app/plugins/datasource/loki/syntax'; +import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; import { RulesSource } from 'app/types/unified-alerting'; + import { DataSourceType, isCloudRulesSource } from '../utils/datasource'; + import { Well } from './Well'; interface Props { diff --git a/public/app/features/alerting/unified/components/PanelPluginsButtonGroup.tsx b/public/app/features/alerting/unified/components/PanelPluginsButtonGroup.tsx index 0f8b9b5486b..d5bada5d1fa 100644 --- a/public/app/features/alerting/unified/components/PanelPluginsButtonGroup.tsx +++ b/public/app/features/alerting/unified/components/PanelPluginsButtonGroup.tsx @@ -1,7 +1,9 @@ +import React, { useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; import { RadioButtonGroup } from '@grafana/ui'; -import React, { useMemo } from 'react'; + import { STAT, TABLE, TIMESERIES } from '../utils/constants'; export type SupportedPanelPlugins = 'timeseries' | 'table' | 'stat'; diff --git a/public/app/features/alerting/unified/components/RuleLocation.tsx b/public/app/features/alerting/unified/components/RuleLocation.tsx new file mode 100644 index 00000000000..74ccbf5209d --- /dev/null +++ b/public/app/features/alerting/unified/components/RuleLocation.tsx @@ -0,0 +1,22 @@ +import React, { FC } from 'react'; + +import { Icon } from '@grafana/ui'; + +interface RuleLocationProps { + namespace: string; + group?: string; +} + +const RuleLocation: FC = ({ namespace, group }) => { + if (!group) { + return <>{namespace}; + } + + return ( + <> + {namespace} {group} + + ); +}; + +export { RuleLocation }; diff --git a/public/app/features/alerting/unified/components/StateColoredText.tsx b/public/app/features/alerting/unified/components/StateColoredText.tsx index 1a650413f23..9e36f2d2d16 100644 --- a/public/app/features/alerting/unified/components/StateColoredText.tsx +++ b/public/app/features/alerting/unified/components/StateColoredText.tsx @@ -1,8 +1,9 @@ +import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; -import { css } from '@emotion/css'; -import React, { FC } from 'react'; type Props = { status: PromAlertingRuleState | 'neutral'; diff --git a/public/app/features/alerting/unified/components/StateTag.tsx b/public/app/features/alerting/unified/components/StateTag.tsx index 5ca76eb73ea..3d4494fd78a 100644 --- a/public/app/features/alerting/unified/components/StateTag.tsx +++ b/public/app/features/alerting/unified/components/StateTag.tsx @@ -1,8 +1,9 @@ -import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; import { css, cx } from '@emotion/css'; import React, { FC } from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + export type State = 'good' | 'bad' | 'warning' | 'neutral' | 'info'; type Props = { diff --git a/public/app/features/alerting/unified/components/TimeToNow.tsx b/public/app/features/alerting/unified/components/TimeToNow.tsx index efd5955bcc9..65cfc2fd75c 100644 --- a/public/app/features/alerting/unified/components/TimeToNow.tsx +++ b/public/app/features/alerting/unified/components/TimeToNow.tsx @@ -1,6 +1,7 @@ -import { dateTimeFormatTimeAgo, DateTimeInput } from '@grafana/data'; import React, { FC, useEffect, useState } from 'react'; +import { dateTimeFormatTimeAgo, DateTimeInput } from '@grafana/data'; + export interface Props { date: DateTimeInput; } diff --git a/public/app/features/alerting/unified/components/Well.tsx b/public/app/features/alerting/unified/components/Well.tsx index 3145298ae68..0ebfed409ca 100644 --- a/public/app/features/alerting/unified/components/Well.tsx +++ b/public/app/features/alerting/unified/components/Well.tsx @@ -1,7 +1,8 @@ -import React, { FC } from 'react'; -import { useStyles } from '@grafana/ui'; import { cx, css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { useStyles } from '@grafana/ui'; type Props = React.HTMLAttributes; diff --git a/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx b/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx index aabb017f588..05cdfebf444 100644 --- a/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx +++ b/public/app/features/alerting/unified/components/admin/AddAlertManagerModal.tsx @@ -1,5 +1,6 @@ -import React, { FC, useMemo } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, useMemo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, Field, FieldArray, Form, Icon, Input, Modal, useStyles2 } from '@grafana/ui'; import { AlertmanagerUrl } from 'app/plugins/datasource/alertmanager/types'; diff --git a/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx b/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx index a4dbf55a55b..26b683d8f7e 100644 --- a/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx +++ b/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.test.tsx @@ -1,34 +1,37 @@ +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; -import { getAllDataSources } from '../../utils/config'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; +import { byLabelText, byRole, byTestId } from 'testing-library-selector'; + +import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { contextSrv } from 'app/core/services/context_srv'; +import store from 'app/core/store'; +import { + AlertManagerCortexConfig, + AlertManagerDataSourceJsonData, + AlertManagerImplementation, +} from 'app/plugins/datasource/alertmanager/types'; +import { configureStore } from 'app/store/configureStore'; + import { fetchAlertManagerConfig, deleteAlertManagerConfig, updateAlertManagerConfig, fetchStatus, } from '../../api/alertmanager'; -import { configureStore } from 'app/store/configureStore'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import AlertmanagerConfig from './AlertmanagerConfig'; -import { Provider } from 'react-redux'; -import { Router } from 'react-router-dom'; -import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../../utils/constants'; -import { render, waitFor } from '@testing-library/react'; -import { byLabelText, byRole, byTestId } from 'testing-library-selector'; import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someCloudAlertManagerStatus, } from '../../mocks'; +import { getAllDataSources } from '../../utils/config'; +import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../../utils/constants'; import { DataSourceType } from '../../utils/datasource'; -import { contextSrv } from 'app/core/services/context_srv'; -import store from 'app/core/store'; -import userEvent from '@testing-library/user-event'; -import { - AlertManagerCortexConfig, - AlertManagerDataSourceJsonData, - AlertManagerImplementation, -} from 'app/plugins/datasource/alertmanager/types'; + +import AlertmanagerConfig from './AlertmanagerConfig'; jest.mock('../../api/alertmanager'); jest.mock('../../api/grafana'); @@ -104,8 +107,8 @@ describe('Admin config', () => { await renderAdminPage(dataSources.alertManager.name); - userEvent.click(await ui.resetButton.find()); - userEvent.click(ui.confirmButton.get()); + await userEvent.click(await ui.resetButton.find()); + await userEvent.click(ui.confirmButton.get()); await waitFor(() => expect(mocks.api.deleteAlertManagerConfig).toHaveBeenCalled()); expect(ui.confirmButton.query()).not.toBeInTheDocument(); }); @@ -132,12 +135,12 @@ describe('Admin config', () => { await renderAdminPage(dataSources.alertManager.name); const input = await ui.configInput.find(); expect(input.value).toEqual(JSON.stringify(defaultConfig, null, 2)); - userEvent.clear(input); + await userEvent.clear(input); // What is this regex replace doing? in userEvent v13, '{' and '[' are special characters. // To get the literal character, you have to escape them by typing '{{' or '[['. // See https://github.com/testing-library/user-event/issues/584. - userEvent.type(input, JSON.stringify(newConfig, null, 2).replace(/[{[]/g, '$&$&')); - userEvent.click(ui.saveButton.get()); + await userEvent.type(input, JSON.stringify(newConfig, null, 2).replace(/[{[]/g, '$&$&')); + await userEvent.click(ui.saveButton.get()); await waitFor(() => expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled()); await waitFor(() => expect(mocks.api.fetchConfig).toHaveBeenCalledTimes(3)); expect(input.value).toEqual(JSON.stringify(newConfig, null, 2)); diff --git a/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx b/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx index 6a939f4372c..aaa09860c15 100644 --- a/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx +++ b/public/app/features/alerting/unified/components/admin/AlertmanagerConfig.tsx @@ -1,18 +1,20 @@ -import React, { useEffect, useState, useMemo } from 'react'; import { css } from '@emotion/css'; +import React, { useEffect, useState, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Button, ConfirmModal, TextArea, HorizontalGroup, Field, Form, useStyles2 } from '@grafana/ui'; + import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName'; -import { AlertManagerPicker } from '../AlertManagerPicker'; -import { GRAFANA_RULES_SOURCE_NAME, isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; -import { useDispatch } from 'react-redux'; +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { deleteAlertManagerConfigAction, fetchAlertManagerConfigAction, updateAlertManagerConfigAction, } from '../../state/actions'; -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { GRAFANA_RULES_SOURCE_NAME, isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; import { initialAsyncRequestState } from '../../utils/redux'; +import { AlertManagerPicker } from '../AlertManagerPicker'; interface FormValues { configJSON: string; diff --git a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx index efcbccf9ab6..f98aff36b96 100644 --- a/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx +++ b/public/app/features/alerting/unified/components/admin/ExternalAlertmanagers.tsx @@ -1,6 +1,7 @@ +import { css, cx } from '@emotion/css'; import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { css, cx } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, @@ -14,14 +15,16 @@ import { useTheme2, } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import { AddAlertManagerModal } from './AddAlertManagerModal'; +import { StoreState } from 'app/types/store'; + +import { useExternalAmSelector } from '../../hooks/useExternalAmSelector'; import { addExternalAlertmanagersAction, fetchExternalAlertmanagersAction, fetchExternalAlertmanagersConfigAction, } from '../../state/actions'; -import { useExternalAmSelector } from '../../hooks/useExternalAmSelector'; -import { StoreState } from 'app/types/store'; + +import { AddAlertManagerModal } from './AddAlertManagerModal'; const alertmanagerChoices = [ { value: 'internal', label: 'Only Internal' }, diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx index 878620f5fd5..8ab58c7cbc7 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx @@ -1,10 +1,12 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { LinkButton, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import { AlertmanagerAlert, AlertState } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; -import React, { FC } from 'react'; + import { getInstancesPermissions } from '../../utils/access-control'; import { isGrafanaRulesSource } from '../../utils/datasource'; import { makeAMLink, makeLabelBasedSilenceLink } from '../../utils/misc'; @@ -24,7 +26,7 @@ export const AlertDetails: FC = ({ alert, aler // For external alert manager the Generator URL redirects to an external service which we don't control const isGrafanaSource = isGrafanaRulesSource(alertManagerSourceName); const isSeeSourceButtonEnabled = isGrafanaSource - ? contextSrv.hasPermission(AccessControlAction.AlertingRuleUpdate) + ? contextSrv.hasPermission(AccessControlAction.AlertingRuleRead) : true; return ( diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx index 073732227a4..c508875b588 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx @@ -1,11 +1,14 @@ -import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types'; +import { css } from '@emotion/css'; import React, { useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { css } from '@emotion/css'; +import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types'; + import { AlertLabels } from '../AlertLabels'; -import { AlertGroupAlertsTable } from './AlertGroupAlertsTable'; import { CollapseToggle } from '../CollapseToggle'; + +import { AlertGroupAlertsTable } from './AlertGroupAlertsTable'; import { AlertGroupHeader } from './AlertGroupHeader'; interface Props { diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx index f6df855efb4..38c0caaeab6 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx @@ -1,12 +1,15 @@ -import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; -import React, { useMemo } from 'react'; -import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data'; import { css } from '@emotion/css'; -import { DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; -import { AmAlertStateTag } from '../silences/AmAlertStateTag'; +import React, { useMemo } from 'react'; + +import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; +import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; + import { AlertLabels } from '../AlertLabels'; +import { DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines'; +import { AmAlertStateTag } from '../silences/AmAlertStateTag'; + import { AlertDetails } from './AlertDetails'; interface Props { diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx index 746f3897d9d..7af9efe0958 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx @@ -1,17 +1,18 @@ +import { css } from '@emotion/css'; import React, { useState } from 'react'; -import { AlertManagerPicker } from '../AlertManagerPicker'; -import { MatcherFilter } from './MatcherFilter'; -import { AlertStateFilter } from './AlertStateFilter'; -import { GroupBy } from './GroupBy'; -import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, useStyles2 } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types'; import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName'; -import { css } from '@emotion/css'; import { getFiltersFromUrlParams } from '../../utils/misc'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { AlertManagerPicker } from '../AlertManagerPicker'; + +import { AlertStateFilter } from './AlertStateFilter'; +import { GroupBy } from './GroupBy'; +import { MatcherFilter } from './MatcherFilter'; interface Props { groups: AlertmanagerGroup[]; diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroupHeader.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroupHeader.tsx index 9f6d9fd7cdb..421bc369b53 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroupHeader.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroupHeader.tsx @@ -1,8 +1,10 @@ -import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types'; -import React from 'react'; -import { useStyles2 } from '@grafana/ui'; -import { getNotificationsTextColors } from '../../styles/notifications'; import pluralize from 'pluralize'; +import React from 'react'; + +import { useStyles2 } from '@grafana/ui'; +import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types'; + +import { getNotificationsTextColors } from '../../styles/notifications'; interface Props { group: AlertmanagerGroup; diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertStateFilter.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertStateFilter.tsx index 44cc945b443..507a4d693c6 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertStateFilter.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertStateFilter.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { RadioButtonGroup, Label, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { AlertState } from 'app/plugins/datasource/alertmanager/types'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { RadioButtonGroup, Label, useStyles2 } from '@grafana/ui'; +import { AlertState } from 'app/plugins/datasource/alertmanager/types'; interface Props { stateFilter?: AlertState; diff --git a/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx b/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx index b43b69847f3..8f4ec0845d9 100644 --- a/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx @@ -1,8 +1,9 @@ -import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; -import React from 'react'; import { uniq } from 'lodash'; -import { Icon, Label, MultiSelect } from '@grafana/ui'; +import React from 'react'; + import { SelectableValue } from '@grafana/data'; +import { Icon, Label, MultiSelect } from '@grafana/ui'; +import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; interface Props { className?: string; diff --git a/public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx b/public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx index 3abd20de39f..c6044a11817 100644 --- a/public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx @@ -1,8 +1,9 @@ -import React, { FormEvent } from 'react'; -import { Label, Tooltip, Input, Icon, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FormEvent } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Stack } from '@grafana/experimental'; +import { Label, Tooltip, Input, Icon, useStyles2 } from '@grafana/ui'; interface Props { className?: string; diff --git a/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx b/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx index e3c1a7d7299..9b12db3a6d5 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRootRoute.tsx @@ -1,13 +1,17 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, useStyles2 } from '@grafana/ui'; + +import { Authorize } from '../../components/Authorize'; import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; +import { getNotificationsPermissions } from '../../utils/access-control'; +import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; + import { AmRootRouteForm } from './AmRootRouteForm'; import { AmRootRouteRead } from './AmRootRouteRead'; -import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; -import { Authorize } from '../../components/Authorize'; -import { getNotificationsPermissions } from '../../utils/access-control'; + export interface AmRootRouteProps { isEditMode: boolean; onEnterEditMode: () => void; diff --git a/public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx b/public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx index 714b63291c1..45dcc38a16d 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRootRouteForm.tsx @@ -1,6 +1,8 @@ -import React, { FC, useState } from 'react'; import { cx } from '@emotion/css'; +import React, { FC, useState } from 'react'; + import { Button, Collapse, Field, Form, Input, InputControl, Link, MultiSelect, Select, useStyles2 } from '@grafana/ui'; + import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; import { mapMultiSelectValueToStrings, @@ -11,6 +13,7 @@ import { } from '../../utils/amroutes'; import { makeAMLink } from '../../utils/misc'; import { timeOptions } from '../../utils/time'; + import { getFormStyles } from './formStyles'; export interface AmRootRouteFormProps { diff --git a/public/app/features/alerting/unified/components/amroutes/AmRootRouteRead.tsx b/public/app/features/alerting/unified/components/amroutes/AmRootRouteRead.tsx index d534bbff303..026e7c6f289 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRootRouteRead.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRootRouteRead.tsx @@ -1,6 +1,9 @@ import React, { FC } from 'react'; + import { useStyles2 } from '@grafana/ui'; + import { FormAmRoute } from '../../types/amroutes'; + import { getGridStyles } from './gridStyles'; export interface AmRootRouteReadProps { diff --git a/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx b/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx index ae7f9a1a142..b7581741faf 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx @@ -1,5 +1,6 @@ -import React, { FC, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, @@ -17,7 +18,10 @@ import { Badge, VerticalGroup, } from '@grafana/ui'; + +import { useMuteTimingOptions } from '../../hooks/useMuteTimingOptions'; import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; +import { matcherFieldOptions } from '../../utils/alertmanager'; import { emptyArrayFieldMatcher, mapMultiSelectValueToStrings, @@ -27,9 +31,8 @@ import { stringsToSelectableValues, } from '../../utils/amroutes'; import { timeOptions } from '../../utils/time'; + import { getFormStyles } from './formStyles'; -import { matcherFieldOptions } from '../../utils/alertmanager'; -import { useMuteTimingOptions } from '../../hooks/useMuteTimingOptions'; export interface AmRoutesExpandedFormProps { onCancel: () => void; diff --git a/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedRead.tsx b/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedRead.tsx index 1ea268ae584..8cfa0270a1c 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedRead.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedRead.tsx @@ -1,14 +1,17 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; import React, { FC, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, useStyles2 } from '@grafana/ui'; + import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; -import { emptyRoute } from '../../utils/amroutes'; -import { AmRoutesTable } from './AmRoutesTable'; -import { getGridStyles } from './gridStyles'; -import { MuteTimingsTable } from './MuteTimingsTable'; -import { Authorize } from '../Authorize'; import { getNotificationsPermissions } from '../../utils/access-control'; +import { emptyRoute } from '../../utils/amroutes'; +import { Authorize } from '../Authorize'; + +import { AmRoutesTable } from './AmRoutesTable'; +import { MuteTimingsTable } from './MuteTimingsTable'; +import { getGridStyles } from './gridStyles'; export interface AmRoutesExpandedReadProps { onChange: (routes: FormAmRoute) => void; diff --git a/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.test.ts b/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.test.ts index a7fcee29adf..c15778b1cee 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.test.ts +++ b/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.test.ts @@ -1,6 +1,8 @@ import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; + import { FormAmRoute } from '../../types/amroutes'; import { MatcherFieldValue } from '../../types/silence-form'; + import { deleteRoute, getFilteredRoutes, updatedRoute } from './AmRoutesTable'; const defaultAmRoute: FormAmRoute = { diff --git a/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx b/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx index 8719728235e..9da90b15ba9 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx @@ -1,16 +1,19 @@ +import { intersectionWith, isEqual } from 'lodash'; import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { Button, HorizontalGroup, IconButton } from '@grafana/ui'; + +import { Button, ConfirmModal, HorizontalGroup, IconButton } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; + import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; +import { getNotificationsPermissions } from '../../utils/access-control'; +import { matcherFieldToMatcher, parseMatchers } from '../../utils/alertmanager'; import { prepareItems } from '../../utils/dynamicTable'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; +import { EmptyArea } from '../EmptyArea'; +import { Matchers } from '../silences/Matchers'; + import { AmRoutesExpandedForm } from './AmRoutesExpandedForm'; import { AmRoutesExpandedRead } from './AmRoutesExpandedRead'; -import { Matchers } from '../silences/Matchers'; -import { matcherFieldToMatcher, parseMatchers } from '../../utils/alertmanager'; -import { intersectionWith, isEqual } from 'lodash'; -import { EmptyArea } from '../EmptyArea'; -import { contextSrv } from 'app/core/services/context_srv'; -import { getNotificationsPermissions } from '../../utils/access-control'; export interface AmRoutesTableProps { isAddMode: boolean; @@ -75,6 +78,7 @@ export const AmRoutesTable: FC = ({ alertManagerSourceName, }) => { const [editMode, setEditMode] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); const [expandedId, setExpandedId] = useState(); const permissions = getNotificationsPermissions(alertManagerSourceName); const canEditRoutes = contextSrv.hasPermission(permissions.update); @@ -135,27 +139,40 @@ export const AmRoutesTable: FC = ({ }; return ( - - - { + <> + + + { + setShowDeleteModal(true); + }} + type="button" + /> + + { const newRoutes = deleteRoute(routes, item.data); onChange(newRoutes); }} - type="button" + onDismiss={() => setShowDeleteModal(false)} /> - + ); }, size: '100px', diff --git a/public/app/features/alerting/unified/components/amroutes/AmSpecificRouting.tsx b/public/app/features/alerting/unified/components/amroutes/AmSpecificRouting.tsx index 7fba03bfc12..cd7dc17006d 100644 --- a/public/app/features/alerting/unified/components/amroutes/AmSpecificRouting.tsx +++ b/public/app/features/alerting/unified/components/amroutes/AmSpecificRouting.tsx @@ -1,19 +1,22 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, Icon, Input, Label, useStyles2 } from '@grafana/ui'; import React, { FC, useState } from 'react'; import { useDebounce } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, Icon, Input, Label, useStyles2 } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; + +import { Authorize } from '../../components/Authorize'; import { useURLSearchParams } from '../../hooks/useURLSearchParams'; import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes'; +import { getNotificationsPermissions } from '../../utils/access-control'; import { emptyArrayFieldMatcher, emptyRoute } from '../../utils/amroutes'; import { getNotificationPoliciesFilters } from '../../utils/misc'; -import { MatcherFilter } from '../alert-groups/MatcherFilter'; import { EmptyArea } from '../EmptyArea'; import { EmptyAreaWithCTA } from '../EmptyAreaWithCTA'; +import { MatcherFilter } from '../alert-groups/MatcherFilter'; + import { AmRoutesTable } from './AmRoutesTable'; -import { Authorize } from '../../components/Authorize'; -import { contextSrv } from 'app/core/services/context_srv'; -import { getNotificationsPermissions } from '../../utils/access-control'; export interface AmSpecificRoutingProps { alertManagerSourceName: string; diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingForm.tsx b/public/app/features/alerting/unified/components/amroutes/MuteTimingForm.tsx index 99f5a6eaf48..49ff77d89d5 100644 --- a/public/app/features/alerting/unified/components/amroutes/MuteTimingForm.tsx +++ b/public/app/features/alerting/unified/components/amroutes/MuteTimingForm.tsx @@ -1,24 +1,27 @@ -import React, { useMemo } from 'react'; -import { AlertingPageWrapper } from '../AlertingPageWrapper'; -import { Alert, Field, FieldSet, Input, Button, LinkButton, useStyles2 } from '@grafana/ui'; -import { FormProvider, useForm } from 'react-hook-form'; -import { GrafanaTheme2 } from '@grafana/data'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { useDispatch } from 'react-redux'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, Field, FieldSet, Input, Button, LinkButton, useStyles2 } from '@grafana/ui'; import { AlertmanagerConfig, AlertManagerCortexConfig, MuteTimeInterval, } from 'app/plugins/datasource/alertmanager/types'; -import { AlertManagerPicker } from '../AlertManagerPicker'; + import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName'; -import { updateAlertManagerConfigAction } from '../../state/actions'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { initialAsyncRequestState } from '../../utils/redux'; +import { updateAlertManagerConfigAction } from '../../state/actions'; import { MuteTimingFields } from '../../types/mute-timing-form'; -import { createMuteTiming, defaultTimeInterval } from '../../utils/mute-timings'; -import { makeAMLink } from '../../utils/misc'; import { renameMuteTimings } from '../../utils/alertmanager'; +import { makeAMLink } from '../../utils/misc'; +import { createMuteTiming, defaultTimeInterval } from '../../utils/mute-timings'; +import { initialAsyncRequestState } from '../../utils/redux'; +import { AlertManagerPicker } from '../AlertManagerPicker'; +import { AlertingPageWrapper } from '../AlertingPageWrapper'; + import { MuteTimingTimeInterval } from './MuteTimingTimeInterval'; interface Props { diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeInterval.tsx b/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeInterval.tsx index 6d23d6be091..4fab679bafd 100644 --- a/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeInterval.tsx +++ b/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeInterval.tsx @@ -1,10 +1,13 @@ +import { css } from '@emotion/css'; import React from 'react'; +import { useFormContext, useFieldArray } from 'react-hook-form'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, Input, Field, FieldSet, useStyles2 } from '@grafana/ui'; -import { css } from '@emotion/css'; -import { useFormContext, useFieldArray } from 'react-hook-form'; -import { DAYS_OF_THE_WEEK, MONTHS, validateArrayField, defaultTimeInterval } from '../../utils/mute-timings'; + import { MuteTimingFields } from '../../types/mute-timing-form'; +import { DAYS_OF_THE_WEEK, MONTHS, validateArrayField, defaultTimeInterval } from '../../utils/mute-timings'; + import { MuteTimingTimeRange } from './MuteTimingTimeRange'; export const MuteTimingTimeInterval = () => { diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeRange.tsx b/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeRange.tsx index f48490f2263..04cba1844b8 100644 --- a/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeRange.tsx +++ b/public/app/features/alerting/unified/components/amroutes/MuteTimingTimeRange.tsx @@ -1,9 +1,11 @@ +import { css } from '@emotion/css'; import React, { FC } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; -import { MuteTimingFields } from '../../types/mute-timing-form'; -import { Field, InlineFieldRow, InlineField, Input, Button, IconButton, useStyles2 } from '@grafana/ui'; + import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; +import { Field, InlineFieldRow, InlineField, Input, Button, IconButton, useStyles2 } from '@grafana/ui'; + +import { MuteTimingFields } from '../../types/mute-timing-form'; interface Props { intervalIndex: number; diff --git a/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx b/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx index 04497c62d28..78c7f5bc61b 100644 --- a/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx +++ b/public/app/features/alerting/unified/components/amroutes/MuteTimingsTable.tsx @@ -1,14 +1,16 @@ -import React, { FC, useMemo, useState } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + import { GrafanaTheme2 } from '@grafana/data'; import { IconButton, LinkButton, Link, useStyles2, ConfirmModal } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; import { AlertManagerCortexConfig, MuteTimeInterval, TimeInterval } from 'app/plugins/datasource/alertmanager/types'; -import { useDispatch } from 'react-redux'; + +import { Authorize } from '../../components/Authorize'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { deleteMuteTimingAction } from '../../state/actions'; -import { makeAMLink } from '../../utils/misc'; -import { AsyncRequestState, initialAsyncRequestState } from '../../utils/redux'; -import { DynamicTable, DynamicTableItemProps, DynamicTableColumnProps } from '../DynamicTable'; +import { getNotificationsPermissions } from '../../utils/access-control'; import { getTimeString, getWeekdayString, @@ -16,10 +18,10 @@ import { getMonthsString, getYearsString, } from '../../utils/alertmanager'; +import { makeAMLink } from '../../utils/misc'; +import { AsyncRequestState, initialAsyncRequestState } from '../../utils/redux'; +import { DynamicTable, DynamicTableItemProps, DynamicTableColumnProps } from '../DynamicTable'; import { EmptyAreaWithCTA } from '../EmptyAreaWithCTA'; -import { Authorize } from '../../components/Authorize'; -import { contextSrv } from 'app/core/services/context_srv'; -import { getNotificationsPermissions } from '../../utils/access-control'; interface Props { alertManagerSourceName: string; diff --git a/public/app/features/alerting/unified/components/amroutes/formStyles.ts b/public/app/features/alerting/unified/components/amroutes/formStyles.ts index 60cde9d1fb9..f61a525c080 100644 --- a/public/app/features/alerting/unified/components/amroutes/formStyles.ts +++ b/public/app/features/alerting/unified/components/amroutes/formStyles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; export const getFormStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/features/alerting/unified/components/amroutes/gridStyles.ts b/public/app/features/alerting/unified/components/amroutes/gridStyles.ts index 4e39d8c6ac7..b5664624545 100644 --- a/public/app/features/alerting/unified/components/amroutes/gridStyles.ts +++ b/public/app/features/alerting/unified/components/amroutes/gridStyles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; export const getGridStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx index 234dc3c3175..6b500e8ab46 100644 --- a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx +++ b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx @@ -1,11 +1,13 @@ -import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import React, { FC } from 'react'; -import { Alert, LinkButton, Button } from '@grafana/ui'; -import { panelToRuleFormValues } from '../../utils/rule-form'; import { useLocation } from 'react-router-dom'; -import { urlUtil } from '@grafana/data'; import { useAsync } from 'react-use'; +import { urlUtil } from '@grafana/data'; +import { Alert, LinkButton, Button } from '@grafana/ui'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; + +import { panelToRuleFormValues } from '../../utils/rule-form'; + interface Props { panel: PanelModel; dashboard: DashboardModel; diff --git a/public/app/features/alerting/unified/components/receivers/EditReceiverView.tsx b/public/app/features/alerting/unified/components/receivers/EditReceiverView.tsx index a40c90ffdf6..dce13077e0b 100644 --- a/public/app/features/alerting/unified/components/receivers/EditReceiverView.tsx +++ b/public/app/features/alerting/unified/components/receivers/EditReceiverView.tsx @@ -1,7 +1,10 @@ +import React, { FC } from 'react'; + import { InfoBox } from '@grafana/ui'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; -import React, { FC } from 'react'; + import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; + import { CloudReceiverForm } from './form/CloudReceiverForm'; import { GrafanaReceiverForm } from './form/GrafanaReceiverForm'; diff --git a/public/app/features/alerting/unified/components/receivers/EditTemplateView.tsx b/public/app/features/alerting/unified/components/receivers/EditTemplateView.tsx index 7b337ab61b5..40cbc96628f 100644 --- a/public/app/features/alerting/unified/components/receivers/EditTemplateView.tsx +++ b/public/app/features/alerting/unified/components/receivers/EditTemplateView.tsx @@ -1,6 +1,8 @@ +import React, { FC } from 'react'; + import { InfoBox } from '@grafana/ui'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; -import React, { FC } from 'react'; + import { TemplateForm } from './TemplateForm'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx b/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx index b7cdf82c130..2a80517d38a 100644 --- a/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx @@ -1,18 +1,21 @@ +import { css } from '@emotion/css'; +import React, { FC } from 'react'; +import { useForm, FormProvider } from 'react-hook-form'; +import { useDispatch } from 'react-redux'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, Button, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; -import React, { FC } from 'react'; + import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { useForm, FormProvider } from 'react-hook-form'; -import { globalConfigOptions } from '../../utils/cloud-alertmanager-notifier-types'; -import { OptionField } from './form/fields/OptionField'; -import { Alert, Button, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; -import { makeAMLink } from '../../utils/misc'; -import { useDispatch } from 'react-redux'; import { updateAlertManagerConfigAction } from '../../state/actions'; -import { omitEmptyValues } from '../../utils/receiver-form'; -import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { globalConfigOptions } from '../../utils/cloud-alertmanager-notifier-types'; import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; +import { makeAMLink } from '../../utils/misc'; +import { omitEmptyValues } from '../../utils/receiver-form'; + +import { OptionField } from './form/fields/OptionField'; interface Props { config: AlertManagerCortexConfig; diff --git a/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx b/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx index 66a605eb97f..86dfea463e8 100644 --- a/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx +++ b/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx @@ -1,6 +1,9 @@ -import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import React, { FC } from 'react'; + +import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; + import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; + import { CloudReceiverForm } from './form/CloudReceiverForm'; import { GrafanaReceiverForm } from './form/GrafanaReceiverForm'; diff --git a/public/app/features/alerting/unified/components/receivers/NewTemplateView.tsx b/public/app/features/alerting/unified/components/receivers/NewTemplateView.tsx index fb529d3427b..a7636dc6a65 100644 --- a/public/app/features/alerting/unified/components/receivers/NewTemplateView.tsx +++ b/public/app/features/alerting/unified/components/receivers/NewTemplateView.tsx @@ -1,5 +1,7 @@ -import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import React, { FC } from 'react'; + +import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; + import { TemplateForm } from './TemplateForm'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversAndTemplatesView.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversAndTemplatesView.tsx index 439b851212d..88d4533b8bf 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversAndTemplatesView.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversAndTemplatesView.tsx @@ -1,12 +1,15 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, LinkButton, useStyles2 } from '@grafana/ui'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; -import React, { FC } from 'react'; + import { GRAFANA_RULES_SOURCE_NAME, isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; import { makeAMLink } from '../../utils/misc'; import { Authorize } from '../Authorize'; + import { ReceiversTable } from './ReceiversTable'; import { TemplatesTable } from './TemplatesTable'; diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx index 2b5fa298445..93c14ce10cd 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx @@ -1,9 +1,10 @@ import { css, cx } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, useStyles2 } from '@grafana/ui'; import React, { FC } from 'react'; import { Link } from 'react-router-dom'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, useStyles2 } from '@grafana/ui'; + interface Props { title: string; description: string; diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversTable.test.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversTable.test.tsx index fa43942699b..3f485589e34 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversTable.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversTable.test.tsx @@ -1,18 +1,21 @@ -import React from 'react'; import { render } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; +import { byRole } from 'testing-library-selector'; + +import { locationService } from '@grafana/runtime'; import { AlertManagerCortexConfig, GrafanaManagedReceiverConfig, Receiver, } from 'app/plugins/datasource/alertmanager/types'; import { configureStore } from 'app/store/configureStore'; -import { Provider } from 'react-redux'; -import { ReceiversTable } from './ReceiversTable'; -import { fetchGrafanaNotifiersAction } from '../../state/actions'; import { NotifierDTO, NotifierType } from 'app/types'; -import { byRole } from 'testing-library-selector'; -import { Router } from 'react-router-dom'; -import { locationService } from '@grafana/runtime'; + +import { fetchGrafanaNotifiersAction } from '../../state/actions'; + +import { ReceiversTable } from './ReceiversTable'; const renderReceieversTable = async (receivers: Receiver[], notifiers: NotifierDTO[]) => { const config: AlertManagerCortexConfig = { diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx index 6a3b6761efa..b0f36628bbe 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversTable.tsx @@ -1,21 +1,24 @@ -import { Button, ConfirmModal, Modal, useStyles2 } from '@grafana/ui'; -import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; +import { css } from '@emotion/css'; import React, { FC, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, ConfirmModal, Modal, useStyles2 } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; +import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; + +import { Authorize } from '../../components/Authorize'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { deleteReceiverAction } from '../../state/actions'; import { getAlertTableStyles } from '../../styles/table'; +import { getNotificationsPermissions } from '../../utils/access-control'; +import { isReceiverUsed } from '../../utils/alertmanager'; +import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; +import { makeAMLink } from '../../utils/misc'; import { extractNotifierTypeCounts } from '../../utils/receivers'; import { ActionIcon } from '../rules/ActionIcon'; + import { ReceiversSection } from './ReceiversSection'; -import { makeAMLink } from '../../utils/misc'; -import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; -import { isReceiverUsed } from '../../utils/alertmanager'; -import { useDispatch } from 'react-redux'; -import { deleteReceiverAction } from '../../state/actions'; -import { isVanillaPrometheusAlertManagerDataSource } from '../../utils/datasource'; -import { Authorize } from '../../components/Authorize'; -import { contextSrv } from 'app/core/services/context_srv'; -import { getNotificationsPermissions } from '../../utils/access-control'; interface Props { config: AlertManagerCortexConfig; diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 073759a560a..6d66b8ad83f 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -1,11 +1,13 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; +import { useForm, Validate } from 'react-hook-form'; +import { useDispatch } from 'react-redux'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Button, Field, Input, LinkButton, TextArea, useStyles2 } from '@grafana/ui'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; -import React, { FC } from 'react'; -import { useForm, Validate } from 'react-hook-form'; -import { useDispatch } from 'react-redux'; + import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { updateAlertManagerConfigAction } from '../../state/actions'; import { makeAMLink } from '../../utils/misc'; diff --git a/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx b/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx index b661c350a98..d49e1ab3c7b 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx @@ -1,17 +1,20 @@ -import { ConfirmModal, useStyles2 } from '@grafana/ui'; -import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import React, { FC, Fragment, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { ConfirmModal, useStyles2 } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; +import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; + +import { Authorize } from '../../components/Authorize'; +import { deleteTemplateAction } from '../../state/actions'; import { getAlertTableStyles } from '../../styles/table'; +import { getNotificationsPermissions } from '../../utils/access-control'; +import { makeAMLink } from '../../utils/misc'; import { CollapseToggle } from '../CollapseToggle'; import { DetailsField } from '../DetailsField'; import { ActionIcon } from '../rules/ActionIcon'; + import { ReceiversSection } from './ReceiversSection'; -import { makeAMLink } from '../../utils/misc'; -import { useDispatch } from 'react-redux'; -import { deleteTemplateAction } from '../../state/actions'; -import { contextSrv } from 'app/core/services/context_srv'; -import { Authorize } from '../../components/Authorize'; -import { getNotificationsPermissions } from '../../utils/access-control'; interface Props { config: AlertManagerCortexConfig; diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx index 875cd515e29..9ca8b98f086 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx @@ -1,10 +1,13 @@ import React from 'react'; -import { Button, Field, Input } from '@grafana/ui'; -import { OptionField } from './fields/OptionField'; -import { ChannelValues, ReceiverFormValues } from '../../../types/receiver-form'; import { useFormContext, FieldError, FieldErrors, DeepMap } from 'react-hook-form'; + +import { Button, Field, Input } from '@grafana/ui'; import { NotificationChannelOption, NotificationChannelSecureFields } from 'app/types'; +import { ChannelValues, ReceiverFormValues } from '../../../types/receiver-form'; + +import { OptionField } from './fields/OptionField'; + export interface Props { defaultValues: R; selectedChannelOptions: NotificationChannelOption[]; @@ -47,12 +50,7 @@ export function ChannelOptions({ value="Configured" suffix={ readOnly ? null : ( - ) @@ -74,7 +72,8 @@ export function ChannelOptions({ readOnly={readOnly} key={key} error={error} - pathPrefix={option.secure ? `${pathPrefix}secureSettings.` : `${pathPrefix}settings.`} + pathPrefix={pathPrefix} + pathSuffix={option.secure ? 'secureSettings.' : 'settings.'} option={option} /> ); diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx index 57cad1a84c6..02c2cfc5a6a 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx @@ -1,13 +1,16 @@ -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { NotifierDTO } from 'app/types'; -import React, { useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/css'; -import { Alert, Button, Field, InputControl, Select, useStyles2 } from '@grafana/ui'; +import React, { useEffect, useMemo, useState } from 'react'; import { useFormContext, FieldErrors } from 'react-hook-form'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { Alert, Button, Field, InputControl, Select, useStyles2 } from '@grafana/ui'; +import { NotifierDTO } from 'app/types'; + +import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; import { ChannelValues, CommonSettingsComponentType } from '../../../types/receiver-form'; + import { ChannelOptions } from './ChannelOptions'; import { CollapsibleSection } from './CollapsibleSection'; -import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; interface Props { defaultValues: R; @@ -37,12 +40,15 @@ export function ChannelSubForm({ }: Props): JSX.Element { const styles = useStyles2(getStyles); const name = (fieldName: string) => `${pathPrefix}${fieldName}`; - const { control, watch, register, trigger, formState } = useFormContext(); + const { control, watch, register, trigger, formState, setValue } = useFormContext(); const selectedType = watch(name('type')) ?? defaultValues.type; // nope, setting "default" does not work at all. const { loading: testingReceiver } = useUnifiedAlertingSelector((state) => state.testReceivers); useEffect(() => { register(`${pathPrefix}.__id`); + /* Need to manually register secureFields or else they'll + be lost when testing a contact point */ + register(`${pathPrefix}.secureFields`); }, [register, pathPrefix]); const [_secureFields, setSecureFields] = useState(secureFields ?? {}); @@ -52,6 +58,7 @@ export function ChannelSubForm({ const updatedSecureFields = { ...secureFields }; delete updatedSecureFields[key]; setSecureFields(updatedSecureFields); + setValue(`${pathPrefix}.secureFields`, updatedSecureFields); } }; diff --git a/public/app/features/alerting/unified/components/receivers/form/CloudCommonChannelSettings.tsx b/public/app/features/alerting/unified/components/receivers/form/CloudCommonChannelSettings.tsx index 53e48f12c33..fc8c56e5202 100644 --- a/public/app/features/alerting/unified/components/receivers/form/CloudCommonChannelSettings.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/CloudCommonChannelSettings.tsx @@ -1,8 +1,10 @@ -import { Checkbox, Field } from '@grafana/ui'; import React, { FC } from 'react'; -import { CommonSettingsComponentProps } from '../../../types/receiver-form'; import { useFormContext } from 'react-hook-form'; +import { Checkbox, Field } from '@grafana/ui'; + +import { CommonSettingsComponentProps } from '../../../types/receiver-form'; + export const CloudCommonChannelSettings: FC = ({ pathPrefix, className, diff --git a/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx index 15c8b97ec1d..1b9bebabbb3 100644 --- a/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx @@ -1,7 +1,9 @@ -import { Alert } from '@grafana/ui'; -import { AlertManagerCortexConfig, Receiver } from 'app/plugins/datasource/alertmanager/types'; import React, { FC, useMemo } from 'react'; import { useDispatch } from 'react-redux'; + +import { Alert } from '@grafana/ui'; +import { AlertManagerCortexConfig, Receiver } from 'app/plugins/datasource/alertmanager/types'; + import { updateAlertManagerConfigAction } from '../../../state/actions'; import { CloudChannelValues, ReceiverFormValues, CloudChannelMap } from '../../../types/receiver-form'; import { cloudNotifierTypes } from '../../../utils/cloud-alertmanager-notifier-types'; @@ -12,6 +14,7 @@ import { formValuesToCloudReceiver, updateConfigWithReceiver, } from '../../../utils/receiver-form'; + import { CloudCommonChannelSettings } from './CloudCommonChannelSettings'; import { ReceiverForm } from './ReceiverForm'; diff --git a/public/app/features/alerting/unified/components/receivers/form/CollapsibleSection.tsx b/public/app/features/alerting/unified/components/receivers/form/CollapsibleSection.tsx index ff80c184290..03972c6b410 100644 --- a/public/app/features/alerting/unified/components/receivers/form/CollapsibleSection.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/CollapsibleSection.tsx @@ -1,7 +1,9 @@ import { css, cx } from '@emotion/css'; +import React, { FC, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { IconSize, useStyles2 } from '@grafana/ui'; -import React, { FC, useState } from 'react'; + import { CollapseToggle } from '../../CollapseToggle'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaCommonChannelSettings.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaCommonChannelSettings.tsx index 1fa79e294f3..f43af3a100b 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaCommonChannelSettings.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaCommonChannelSettings.tsx @@ -1,8 +1,10 @@ -import { Checkbox, Field } from '@grafana/ui'; import React, { FC } from 'react'; -import { CommonSettingsComponentProps } from '../../../types/receiver-form'; import { useFormContext } from 'react-hook-form'; +import { Checkbox, Field } from '@grafana/ui'; + +import { CommonSettingsComponentProps } from '../../../types/receiver-form'; + export const GrafanaCommonChannelSettings: FC = ({ pathPrefix, className }) => { const { register } = useFormContext(); return ( diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx index 36528cebe9d..71eaae86162 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx @@ -1,3 +1,6 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; + import { LoadingPlaceholder } from '@grafana/ui'; import { AlertManagerCortexConfig, @@ -5,8 +8,7 @@ import { Receiver, TestReceiversAlert, } from 'app/plugins/datasource/alertmanager/types'; -import React, { FC, useEffect, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; + import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; import { fetchGrafanaNotifiersAction, @@ -21,6 +23,7 @@ import { grafanaReceiverToFormValues, updateConfigWithReceiver, } from '../../../utils/receiver-form'; + import { GrafanaCommonChannelSettings } from './GrafanaCommonChannelSettings'; import { ReceiverForm } from './ReceiverForm'; import { TestContactPointModal } from './TestContactPointModal'; diff --git a/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx index 553a6e13c86..bfde9156c91 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx @@ -1,19 +1,22 @@ import { css } from '@emotion/css'; +import React, { useCallback } from 'react'; +import { useForm, FormProvider, FieldErrors, Validate } from 'react-hook-form'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Button, Field, Input, LinkButton, useStyles2 } from '@grafana/ui'; +import { useAppNotification } from 'app/core/copy/appNotification'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import { NotifierDTO } from 'app/types'; -import React, { useCallback } from 'react'; -import { useForm, FormProvider, FieldErrors, Validate } from 'react-hook-form'; + import { useControlledFieldArray } from '../../../hooks/useControlledFieldArray'; import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; import { ChannelValues, CommonSettingsComponentType, ReceiverFormValues } from '../../../types/receiver-form'; +import { isVanillaPrometheusAlertManagerDataSource } from '../../../utils/datasource'; import { makeAMLink } from '../../../utils/misc'; + import { ChannelSubForm } from './ChannelSubForm'; import { DeletedSubForm } from './fields/DeletedSubform'; -import { isVanillaPrometheusAlertManagerDataSource } from '../../../utils/datasource'; -import { useAppNotification } from 'app/core/copy/appNotification'; interface Props { config: AlertManagerCortexConfig; diff --git a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx index 1c580b64902..a552329b90e 100644 --- a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx @@ -1,12 +1,14 @@ -import React, { useState } from 'react'; -import { Modal, Button, Label, useStyles2, RadioButtonGroup } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import { useForm, FormProvider } from 'react-hook-form'; -import { TestReceiversAlert } from 'app/plugins/datasource/alertmanager/types'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; +import { useForm, FormProvider } from 'react-hook-form'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Modal, Button, Label, useStyles2, RadioButtonGroup } from '@grafana/ui'; +import { TestReceiversAlert } from 'app/plugins/datasource/alertmanager/types'; +import { Annotations, Labels } from 'app/types/unified-alerting-dto'; + import AnnotationsField from '../../rule-editor/AnnotationsField'; import LabelsField from '../../rule-editor/LabelsField'; -import { Annotations, Labels } from 'app/types/unified-alerting-dto'; interface Props { isOpen: boolean; diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx index 91a5b5e7f86..4bf2dda0b50 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx @@ -1,7 +1,9 @@ -import React, { FC, useEffect, useState } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC, useEffect, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, Input, useStyles2 } from '@grafana/ui'; + import { ActionIcon } from '../../../rules/ActionIcon'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx index 3d6c6df0329..f0f12519d6d 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx @@ -1,23 +1,37 @@ +import { css } from '@emotion/css'; +import { isEmpty } from 'lodash'; import React, { FC, useEffect } from 'react'; +import { useFormContext, FieldError, DeepMap } from 'react-hook-form'; + import { Checkbox, Field, Input, InputControl, Select, TextArea } from '@grafana/ui'; import { NotificationChannelOption } from 'app/types'; -import { useFormContext, FieldError, DeepMap } from 'react-hook-form'; -import { SubformField } from './SubformField'; -import { css } from '@emotion/css'; + import { KeyValueMapInput } from './KeyValueMapInput'; -import { SubformArrayField } from './SubformArrayField'; import { StringArrayInput } from './StringArrayInput'; +import { SubformArrayField } from './SubformArrayField'; +import { SubformField } from './SubformField'; interface Props { defaultValue: any; option: NotificationChannelOption; invalid?: boolean; pathPrefix: string; + pathSuffix?: string; error?: FieldError | DeepMap; readOnly?: boolean; } -export const OptionField: FC = ({ option, invalid, pathPrefix, error, defaultValue, readOnly = false }) => { +export const OptionField: FC = ({ + option, + invalid, + pathPrefix, + pathSuffix = '', + error, + defaultValue, + readOnly = false, +}) => { + const optionPath = `${pathPrefix}${pathSuffix}`; + if (option.element === 'subform') { return ( = ({ option, invalid, pathPrefix, error, def defaultValue={defaultValue} option={option} errors={error as DeepMap | undefined} - pathPrefix={pathPrefix} + pathPrefix={optionPath} /> ); } @@ -35,7 +49,7 @@ export const OptionField: FC = ({ option, invalid, pathPrefix, error, def readOnly={readOnly} defaultValues={defaultValue} option={option} - pathPrefix={pathPrefix} + pathPrefix={optionPath} errors={error as Array> | undefined} /> ); @@ -48,18 +62,26 @@ export const OptionField: FC = ({ option, invalid, pathPrefix, error, def error={error?.message} > ); }; -const OptionInput: FC = ({ option, invalid, id, pathPrefix = '', readOnly = false }) => { +const OptionInput: FC = ({ + option, + invalid, + id, + pathPrefix = '', + pathIndex = '', + readOnly = false, +}) => { const { control, register, unregister, getValues } = useFormContext(); const name = `${pathPrefix}${option.propertyName}`; @@ -87,11 +109,11 @@ const OptionInput: FC = ({ option, invalid, id, pathPref return ( (option.validationRule !== '' ? validateOption(v, option.validationRule) : true), })} placeholder={option.placeholder} @@ -165,19 +187,26 @@ const validateOption = (value: string, validationRule: string) => { return RegExp(validationRule).test(value) ? true : 'Invalid format'; }; -const determineRequired = (option: NotificationChannelOption, getValues: any) => { +const determineRequired = (option: NotificationChannelOption, getValues: any, pathIndex: string) => { if (!option.dependsOn) { return option.required ? 'Required' : false; } - - const dependentOn = getValues(`items[0].${option.dependsOn}`); - return !dependentOn && option.required ? 'Required' : false; + if (isEmpty(getValues(`${pathIndex}secureFields`))) { + const dependentOn = getValues(`${pathIndex}secureSettings.${option.dependsOn}`); + return !Boolean(dependentOn) && option.required ? 'Required' : false; + } else { + const dependentOn: boolean = getValues(`${pathIndex}secureFields.${option.dependsOn}`); + return !dependentOn && option.required ? 'Required' : false; + } }; -const determineReadOnly = (option: NotificationChannelOption, getValues: any) => { +const determineReadOnly = (option: NotificationChannelOption, getValues: any, pathIndex: string) => { if (!option.dependsOn) { return false; } - - return getValues(`items[0].${option.dependsOn}`); + if (isEmpty(getValues(`${pathIndex}secureFields`))) { + return getValues(`${pathIndex}secureSettings.${option.dependsOn}`); + } else { + return getValues(`${pathIndex}secureFields.${option.dependsOn}`); + } }; diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx index 8ff879e9b25..a752fea4482 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx @@ -1,7 +1,9 @@ -import React, { FC } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, Input, useStyles2 } from '@grafana/ui'; + import { ActionIcon } from '../../../rules/ActionIcon'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx index 935118e7594..103b35f8563 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx @@ -1,11 +1,14 @@ import React, { FC } from 'react'; -import { NotificationChannelOption } from 'app/types'; import { FieldError, DeepMap, useFormContext } from 'react-hook-form'; + import { Button, useStyles2 } from '@grafana/ui'; -import { CollapsibleSection } from '../CollapsibleSection'; -import { ActionIcon } from '../../../rules/ActionIcon'; -import { OptionField } from './OptionField'; import { useControlledFieldArray } from 'app/features/alerting/unified/hooks/useControlledFieldArray'; +import { NotificationChannelOption } from 'app/types'; + +import { ActionIcon } from '../../../rules/ActionIcon'; +import { CollapsibleSection } from '../CollapsibleSection'; + +import { OptionField } from './OptionField'; import { getReceiverFormFieldStyles } from './styles'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx index 7707b9f4e99..626c859cb57 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx @@ -1,9 +1,12 @@ import React, { FC, useState } from 'react'; -import { NotificationChannelOption } from 'app/types'; import { FieldError, DeepMap, useFormContext } from 'react-hook-form'; -import { OptionField } from './OptionField'; + import { Button, useStyles2 } from '@grafana/ui'; +import { NotificationChannelOption } from 'app/types'; + import { ActionIcon } from '../../../rules/ActionIcon'; + +import { OptionField } from './OptionField'; import { getReceiverFormFieldStyles } from './styles'; interface Props { diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/styles.ts b/public/app/features/alerting/unified/components/receivers/form/fields/styles.ts index 28709bf3223..baae5279258 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/styles.ts +++ b/public/app/features/alerting/unified/components/receivers/form/fields/styles.ts @@ -1,6 +1,7 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; + export const getReceiverFormFieldStyles = (theme: GrafanaTheme2) => ({ collapsibleSection: css` margin: 0; diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx index ee51d1bba84..cd536a9f80b 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx @@ -1,28 +1,28 @@ +import { css } from '@emotion/css'; import React, { FC, useMemo, useState } from 'react'; +import { useForm, FormProvider, UseFormWatch } from 'react-hook-form'; +import { useDispatch } from 'react-redux'; +import { Link } from 'react-router-dom'; + import { GrafanaTheme2 } from '@grafana/data'; import { PageToolbar, Button, useStyles2, CustomScrollbar, Spinner, ConfirmModal } from '@grafana/ui'; -import { css } from '@emotion/css'; +import { useAppNotification } from 'app/core/copy/appNotification'; +import { useCleanup } from 'app/core/hooks/useCleanup'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { RuleWithLocation } from 'app/types/unified-alerting'; + +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { deleteRuleAction, saveRuleFormAction } from '../../state/actions'; +import { RuleFormType, RuleFormValues } from '../../types/rule-form'; +import { initialAsyncRequestState } from '../../utils/redux'; +import { rulerRuleToFormValues, getDefaultFormValues, getDefaultQueries } from '../../utils/rule-form'; +import * as ruleId from '../../utils/rule-id'; import { AlertTypeStep } from './AlertTypeStep'; -import { DetailsStep } from './DetailsStep'; -import { QueryStep } from './QueryStep'; -import { useForm, FormProvider, UseFormWatch } from 'react-hook-form'; - -import { RuleFormType, RuleFormValues } from '../../types/rule-form'; -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { initialAsyncRequestState } from '../../utils/redux'; -import { deleteRuleAction, saveRuleFormAction } from '../../state/actions'; -import { RuleWithLocation } from 'app/types/unified-alerting'; -import { useDispatch } from 'react-redux'; -import { useCleanup } from 'app/core/hooks/useCleanup'; -import { rulerRuleToFormValues, getDefaultFormValues, getDefaultQueries } from '../../utils/rule-form'; -import { Link } from 'react-router-dom'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { useAppNotification } from 'app/core/copy/appNotification'; - import { CloudConditionsStep } from './CloudConditionsStep'; +import { DetailsStep } from './DetailsStep'; import { GrafanaConditionsStep } from './GrafanaConditionsStep'; -import * as ruleId from '../../utils/rule-id'; +import { QueryStep } from './QueryStep'; import { RuleInspector } from './RuleInspector'; type Props = { diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.test.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.test.tsx index 7eacd6a1fcb..8ddf07be1c1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.test.tsx @@ -1,11 +1,13 @@ import { render } from '@testing-library/react'; -import { contextSrv } from 'app/core/services/context_srv'; -import { configureStore } from 'app/store/configureStore'; -import { AccessControlAction } from 'app/types'; import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { Provider } from 'react-redux'; import { byText } from 'testing-library-selector'; + +import { contextSrv } from 'app/core/services/context_srv'; +import { configureStore } from 'app/store/configureStore'; +import { AccessControlAction } from 'app/types'; + import { AlertTypeStep } from './AlertTypeStep'; const ui = { @@ -33,7 +35,7 @@ function renderAlertTypeStep() { } describe('RuleTypePicker', () => { - describe('FGAC', () => { + describe('RBAC', () => { it('Should display grafana, mimir alert and mimir recording buttons when user has rule create and write permissions', async () => { jest.spyOn(contextSrv, 'hasPermission').mockImplementation((action) => { return [AccessControlAction.AlertingRuleCreate, AccessControlAction.AlertingRuleExternalWrite].includes( diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.tsx index c0ebdaa73bf..e1d6963590b 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertTypeStep.tsx @@ -1,18 +1,22 @@ -import React, { FC } from 'react'; -import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; -import { Field, Input, InputControl, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; -import { RuleEditorSection } from './RuleEditorSection'; +import React, { FC } from 'react'; import { useFormContext } from 'react-hook-form'; -import { RuleFormType, RuleFormValues } from '../../types/rule-form'; -import { Folder, RuleFolderPicker } from './RuleFolderPicker'; -import { GroupAndNamespaceFields } from './GroupAndNamespaceFields'; -import { CloudRulesSourcePicker } from './CloudRulesSourcePicker'; -import { checkForPathSeparator } from './util'; -import { RuleTypePicker } from './rule-types/RuleTypePicker'; + +import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; +import { Stack } from '@grafana/experimental'; +import { Field, Icon, Input, InputControl, Label, Tooltip, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; +import { RuleFormType, RuleFormValues } from '../../types/rule-form'; + +import { CloudRulesSourcePicker } from './CloudRulesSourcePicker'; +import { GroupAndNamespaceFields } from './GroupAndNamespaceFields'; +import { RuleEditorSection } from './RuleEditorSection'; +import { Folder, RuleFolderPicker } from './RuleFolderPicker'; +import { RuleTypePicker } from './rule-types/RuleTypePicker'; +import { checkForPathSeparator } from './util'; + interface Props { editingExistingRule: boolean; } @@ -120,26 +124,60 @@ export const AlertTypeStep: FC = ({ editingExistingRule }) => { dataSourceName && } {ruleFormType === RuleFormType.grafana && ( - - ( - - )} - name="folder" - rules={{ - required: { value: true, message: 'Please select a folder' }, - validate: { - pathSeparator: (folder: Folder) => checkForPathSeparator(folder.title), - }, - }} - /> - +
+ + + Folder + + Each folder has unique folder permission. When you store multiple rules in a folder, the folder + access permissions get assigned to the rules. +
+ } + > + + + + + } + className={styles.formInput} + error={errors.folder?.message} + invalid={!!errors.folder?.message} + data-testid="folder-picker" + > + ( + + )} + name="folder" + rules={{ + required: { value: true, message: 'Please select a folder' }, + validate: { + pathSeparator: (folder: Folder) => checkForPathSeparator(folder.title), + }, + }} + /> + + + + +
)} ); @@ -172,5 +210,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ display: flex; flex-direction: row; justify-content: flex-start; + align-items: flex-end; `, }); diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx index 7451454b145..f9c433ce397 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationKeyInput.tsx @@ -1,8 +1,11 @@ -import { SelectableValue } from '@grafana/data'; import React, { FC, useMemo } from 'react'; -import { SelectWithAdd } from './SelectWIthAdd'; + +import { SelectableValue } from '@grafana/data'; + import { Annotation, annotationLabels } from '../../utils/constants'; +import { SelectWithAdd } from './SelectWIthAdd'; + interface Props { onChange: (value: string) => void; existingKeys: string[]; diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx index da1b3aef463..1fce0edf88e 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx @@ -1,9 +1,12 @@ -import React, { FC, useCallback } from 'react'; -import { Button, Field, FieldArray, Input, InputControl, Label, TextArea, useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { FC, useCallback } from 'react'; import { useFormContext } from 'react-hook-form'; + +import { GrafanaTheme } from '@grafana/data'; +import { Button, Field, FieldArray, Input, InputControl, Label, TextArea, useStyles } from '@grafana/ui'; + import { RuleFormValues } from '../../types/rule-form'; + import { AnnotationKeyInput } from './AnnotationKeyInput'; const AnnotationsField: FC = () => { diff --git a/public/app/features/alerting/unified/components/rule-editor/CloudConditionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/CloudConditionsStep.tsx index 91cc9fcc1d4..3323d9b4c15 100644 --- a/public/app/features/alerting/unified/components/rule-editor/CloudConditionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/CloudConditionsStep.tsx @@ -1,12 +1,15 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; +import { useFormContext } from 'react-hook-form'; + import { GrafanaTheme } from '@grafana/data'; import { Field, Input, InputControl, Select, useStyles } from '@grafana/ui'; -import { useFormContext } from 'react-hook-form'; + import { RuleFormType, RuleFormValues } from '../../types/rule-form'; import { timeOptions } from '../../utils/time'; -import { RuleEditorSection } from './RuleEditorSection'; + import { PreviewRule } from './PreviewRule'; +import { RuleEditorSection } from './RuleEditorSection'; export const CloudConditionsStep: FC = () => { const styles = useStyles(getStyles); diff --git a/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx b/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx index 7668d836af3..b995cdfdd49 100644 --- a/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/CloudRulesSourcePicker.tsx @@ -1,6 +1,8 @@ import React, { useCallback } from 'react'; + import { DataSourceInstanceSettings } from '@grafana/data'; import { DataSourcePicker } from '@grafana/runtime'; + import { useRulesSourcesWithRuler } from '../../hooks/useRuleSourcesWithRuler'; interface Props { diff --git a/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx b/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx index f4341f35132..9c45e41bf51 100644 --- a/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/ConditionField.tsx @@ -1,8 +1,10 @@ +import React, { FC, useEffect, useMemo } from 'react'; +import { useFormContext } from 'react-hook-form'; + import { SelectableValue } from '@grafana/data'; import { Field, InputControl, Select } from '@grafana/ui'; import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; -import React, { FC, useEffect, useMemo } from 'react'; -import { useFormContext } from 'react-hook-form'; + import { RuleFormValues } from '../../types/rule-form'; export const ConditionField: FC = () => { diff --git a/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx index 2f727086285..6cecff78b31 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx @@ -1,10 +1,12 @@ import React, { FC } from 'react'; -import LabelsField from './LabelsField'; -import AnnotationsField from './AnnotationsField'; -import { RuleEditorSection } from './RuleEditorSection'; import { useFormContext } from 'react-hook-form'; + import { RuleFormType, RuleFormValues } from '../../types/rule-form'; +import AnnotationsField from './AnnotationsField'; +import LabelsField from './LabelsField'; +import { RuleEditorSection } from './RuleEditorSection'; + export const DetailsStep: FC = () => { const { watch } = useFormContext(); diff --git a/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx index 626e1f58114..e17ce8b6c6a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx @@ -1,10 +1,11 @@ -import React, { FC, useCallback, useMemo, useState } from 'react'; import { noop } from 'lodash'; +import React, { FC, useCallback, useMemo, useState } from 'react'; +import { useAsync } from 'react-use'; + import { CoreApp, DataQuery } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; -import { useAsync } from 'react-use'; -import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { LokiQuery } from 'app/plugins/datasource/loki/types'; +import { PromQuery } from 'app/plugins/datasource/prometheus/types'; export interface ExpressionEditorProps { value?: string; diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaAlertStatePicker.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaAlertStatePicker.tsx index 42eab78203a..664306d2a34 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaAlertStatePicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaAlertStatePicker.tsx @@ -1,8 +1,9 @@ +import React, { FC, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; import { SelectBaseProps } from '@grafana/ui/src/components/Select/types'; import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; -import React, { FC, useMemo } from 'react'; type Props = Omit, 'options'> & { includeNoData: boolean; diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionEvalWarning.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionEvalWarning.tsx index a51b350516c..0de1c1d6122 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionEvalWarning.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionEvalWarning.tsx @@ -1,8 +1,10 @@ -import { durationToMilliseconds, parseDuration } from '@grafana/data'; -import { Alert } from '@grafana/ui'; import { isEmpty } from 'lodash'; import React, { FC } from 'react'; import { useFormContext } from 'react-hook-form'; + +import { durationToMilliseconds, parseDuration } from '@grafana/data'; +import { Alert } from '@grafana/ui'; + import { RuleFormValues } from '../../types/rule-form'; // a warning that will be shown if a problematic yet technically valid combination of "evaluate every" and "evaluate for" is enetered diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionsStep.tsx index cbde71df334..f5cd8533662 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaConditionsStep.tsx @@ -1,16 +1,19 @@ -import React, { FC, useState } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useState } from 'react'; +import { useFormContext, RegisterOptions } from 'react-hook-form'; + import { parseDuration, durationToMilliseconds, GrafanaTheme2 } from '@grafana/data'; import { Field, InlineLabel, Input, InputControl, useStyles2 } from '@grafana/ui'; -import { useFormContext, RegisterOptions } from 'react-hook-form'; + import { RuleFormValues } from '../../types/rule-form'; import { positiveDurationValidationPattern, durationValidationPattern } from '../../utils/time'; +import { CollapseToggle } from '../CollapseToggle'; + import { ConditionField } from './ConditionField'; import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker'; -import { RuleEditorSection } from './RuleEditorSection'; -import { PreviewRule } from './PreviewRule'; import { GrafanaConditionEvalWarning } from './GrafanaConditionEvalWarning'; -import { CollapseToggle } from '../CollapseToggle'; +import { PreviewRule } from './PreviewRule'; +import { RuleEditorSection } from './RuleEditorSection'; const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds @@ -57,7 +60,10 @@ export const GrafanaConditionsStep: FC = () => { return ( - +
{ > Evaluate every - - - + = ['type', 'dataSourceName', 'condition', 'queries', 'expression']; diff --git a/public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx b/public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx index 0e77dbe8e67..94b22e376b9 100644 --- a/public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { TableCellDisplayMode, useStyles2 } from '@grafana/ui'; -import { PanelRenderer } from '@grafana/runtime'; + import { FieldConfigSource, FieldMatcherID, GrafanaTheme2, LoadingState } from '@grafana/data'; +import { PanelRenderer } from '@grafana/runtime'; +import { TableCellDisplayMode, useStyles2 } from '@grafana/ui'; + import { PreviewRuleResponse } from '../../types/preview'; import { RuleFormType } from '../../types/rule-form'; import { messageFromError } from '../../utils/redux'; diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx index 3eb91a9d545..66226f41c57 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryEditor.tsx @@ -1,5 +1,6 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + import { DataQuery, getDefaultRelativeTimeRange, @@ -9,20 +10,22 @@ import { RelativeTimeRange, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Button, HorizontalGroup, stylesFactory } from '@grafana/ui'; import { config } from '@grafana/runtime'; -import { QueryRows } from './QueryRows'; +import { Button, HorizontalGroup, stylesFactory } from '@grafana/ui'; +import { getNextRefIdChar } from 'app/core/utils/query'; import { dataSource as expressionDatasource, ExpressionDatasourceUID, } from 'app/features/expressions/ExpressionDatasource'; -import { getNextRefIdChar } from 'app/core/utils/query'; -import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; -import { ExpressionQueryType } from 'app/features/expressions/types'; -import { AlertQuery } from 'app/types/unified-alerting-dto'; -import { AlertingQueryRunner } from '../../state/AlertingQueryRunner'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { isExpressionQuery } from 'app/features/expressions/guards'; +import { ExpressionQueryType } from 'app/features/expressions/types'; +import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { AlertingQueryRunner } from '../../state/AlertingQueryRunner'; + +import { QueryRows } from './QueryRows'; interface Props { value?: AlertQuery[]; diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx index d6d7a653dc4..17a7140bc80 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx @@ -1,5 +1,7 @@ +import { omit } from 'lodash'; import React, { PureComponent, useState } from 'react'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; + import { DataQuery, DataSourceInstanceSettings, @@ -10,14 +12,14 @@ import { ThresholdsMode, } from '@grafana/data'; import { config, getDataSourceSrv } from '@grafana/runtime'; -import { EmptyQueryWrapper, QueryWrapper } from './QueryWrapper'; -import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; -import { isExpressionQuery } from 'app/features/expressions/guards'; -import { queriesWithUpdatedReferences } from './util'; import { Button, Card, Icon } from '@grafana/ui'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; +import { isExpressionQuery } from 'app/features/expressions/guards'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { omit } from 'lodash'; +import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto'; + +import { EmptyQueryWrapper, QueryWrapper } from './QueryWrapper'; +import { queriesWithUpdatedReferences } from './util'; interface Props { // The query configuration diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryStep.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryStep.tsx index e55f4d5d549..3f896e9a18e 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryStep.tsx @@ -1,10 +1,13 @@ import React, { FC } from 'react'; import { useFormContext } from 'react-hook-form'; + import { Field, InputControl } from '@grafana/ui'; -import { ExpressionEditor } from './ExpressionEditor'; -import { RuleEditorSection } from './RuleEditorSection'; + import { RuleFormType, RuleFormValues } from '../../types/rule-form'; + +import { ExpressionEditor } from './ExpressionEditor'; import { QueryEditor } from './QueryEditor'; +import { RuleEditorSection } from './RuleEditorSection'; export const QueryStep: FC = () => { const { diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx index 5c7148e77ee..8a95fa6087c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx @@ -1,6 +1,7 @@ -import React, { FC, ReactNode, useState } from 'react'; import { css } from '@emotion/css'; import { cloneDeep } from 'lodash'; +import React, { FC, ReactNode, useState } from 'react'; + import { CoreApp, DataQuery, @@ -13,12 +14,14 @@ import { ThresholdsConfig, } from '@grafana/data'; import { RelativeTimeRangePicker, useStyles2 } from '@grafana/ui'; -import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow'; -import { VizWrapper } from './VizWrapper'; import { isExpressionQuery } from 'app/features/expressions/guards'; +import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + import { TABLE, TIMESERIES } from '../../utils/constants'; import { SupportedPanelPlugins } from '../PanelPluginsButtonGroup'; -import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { VizWrapper } from './VizWrapper'; interface Props { data: PanelData; diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx index 220e314cd76..23943eb4034 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { FieldSet, useStyles2 } from '@grafana/ui'; -import React, { FC } from 'react'; export interface RuleEditorSectionProps { title: string; diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx index c608bec4b64..1e65372a238 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleFolderPicker.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { FolderPicker, Props as FolderPickerProps } from 'app/core/components/Select/FolderPicker'; export interface Folder { diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx index 02803227f10..1b50995dc61 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx @@ -1,10 +1,12 @@ +import { css } from '@emotion/css'; +import { dump, load } from 'js-yaml'; import React, { FC, useState } from 'react'; import { useFormContext } from 'react-hook-form'; -import { dump, load } from 'js-yaml'; -import { css } from '@emotion/css'; import AutoSizer from 'react-virtualized-auto-sizer'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, CodeEditor, Drawer, Tab, TabsBar, useStyles2 } from '@grafana/ui'; + import { RuleFormValues } from '../../types/rule-form'; interface Props { diff --git a/public/app/features/alerting/unified/components/rule-editor/SelectWIthAdd.tsx b/public/app/features/alerting/unified/components/rule-editor/SelectWIthAdd.tsx index 2c99cf8add0..056310630b0 100644 --- a/public/app/features/alerting/unified/components/rule-editor/SelectWIthAdd.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/SelectWIthAdd.tsx @@ -1,6 +1,7 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Input, Select } from '@grafana/ui'; -import React, { FC, useEffect, useMemo, useState } from 'react'; interface Props { onChange: (value: string) => void; diff --git a/public/app/features/alerting/unified/components/rule-editor/VizWrapper.tsx b/public/app/features/alerting/unified/components/rule-editor/VizWrapper.tsx index d826ec6439f..09541382bcd 100644 --- a/public/app/features/alerting/unified/components/rule-editor/VizWrapper.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/VizWrapper.tsx @@ -1,14 +1,16 @@ +import { css } from '@emotion/css'; import React, { FC, useEffect, useMemo, useState } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { css } from '@emotion/css'; + import { FieldConfigSource, GrafanaTheme2, PanelData, ThresholdsConfig } from '@grafana/data'; import { PanelRenderer } from '@grafana/runtime'; -import { PanelContext, PanelContextProvider, useStyles2 } from '@grafana/ui'; import { GraphFieldConfig, GraphTresholdsStyleMode } from '@grafana/schema'; +import { PanelContext, PanelContextProvider, useStyles2 } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; import { PanelOptions } from 'app/plugins/panel/table/models.gen'; + import { useVizHeight } from '../../hooks/useVizHeight'; import { SupportedPanelPlugins, PanelPluginsButtonGroup } from '../PanelPluginsButtonGroup'; -import appEvents from 'app/core/app_events'; interface Props { data: PanelData; diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx index 25110586a46..db6699e4617 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/DisabledTooltip.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { Tooltip } from '@grafana/ui'; type Props = { diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx index 0c565856472..b1558560c96 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/GrafanaManagedAlert.tsx @@ -1,7 +1,9 @@ import React, { FC } from 'react'; -import { RuleType, SharedProps } from './RuleType'; + import { RuleFormType } from '../../../types/rule-form'; +import { RuleType, SharedProps } from './RuleType'; + const GrafanaManagedRuleType: FC = ({ selected = false, disabled, onClick }) => { return ( void; } diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx index d07836555e4..05519e7dcd1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/MimirOrLokiRecordingRule.tsx @@ -1,8 +1,10 @@ import React, { FC } from 'react'; -import { RuleType, SharedProps } from './RuleType'; -import { DisabledTooltip } from './DisabledTooltip'; + import { RuleFormType } from '../../../types/rule-form'; +import { DisabledTooltip } from './DisabledTooltip'; +import { RuleType, SharedProps } from './RuleType'; + const RecordingRuleType: FC = ({ selected = false, disabled = false, onClick }) => { return ( diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx index 066e000e7cf..258617c1730 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx @@ -1,7 +1,9 @@ import { css, cx } from '@emotion/css'; +import React, { FC, ReactNode } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Card, useStyles2 } from '@grafana/ui'; -import React, { FC, ReactNode } from 'react'; + import { RuleFormType } from '../../../types/rule-form'; interface Props extends SharedProps { diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx index e9dcc0c3450..f92eaede6a1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleTypePicker.tsx @@ -1,11 +1,14 @@ import { css } from '@emotion/css'; +import { isEmpty } from 'lodash'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data/src'; import { Stack } from '@grafana/experimental'; import { useStyles2 } from '@grafana/ui'; -import { isEmpty } from 'lodash'; -import React, { FC } from 'react'; + import { useRulesSourcesWithRuler } from '../../../hooks/useRuleSourcesWithRuler'; import { RuleFormType } from '../../../types/rule-form'; + import { GrafanaManagedRuleType } from './GrafanaManagedAlert'; import { MimirFlavoredType } from './MimirOrLokiAlert'; import { RecordingRuleType } from './MimirOrLokiRecordingRule'; diff --git a/public/app/features/alerting/unified/components/rule-editor/util.test.ts b/public/app/features/alerting/unified/components/rule-editor/util.test.ts index 33643e09b65..2dec00d720a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/util.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/util.test.ts @@ -1,7 +1,8 @@ +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/types'; import { AlertQuery } from 'app/types/unified-alerting-dto'; + import { checkForPathSeparator, queriesWithUpdatedReferences, updateMathExpressionRefs } from './util'; -import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; describe('rule-editor', () => { const dataSource: AlertQuery = { diff --git a/public/app/features/alerting/unified/components/rule-editor/util.ts b/public/app/features/alerting/unified/components/rule-editor/util.ts index a6921c956f6..b623941723f 100644 --- a/public/app/features/alerting/unified/components/rule-editor/util.ts +++ b/public/app/features/alerting/unified/components/rule-editor/util.ts @@ -1,6 +1,7 @@ +import { ValidateResult } from 'react-hook-form'; + import { isExpressionQuery } from 'app/features/expressions/guards'; import { AlertQuery } from 'app/types/unified-alerting-dto'; -import { ValidateResult } from 'react-hook-form'; export function queriesWithUpdatedReferences( queries: AlertQuery[], diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx index 1dff5a1807d..1306b7566f4 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewerLayout.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { PageToolbar, useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewerVisualization.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewerVisualization.tsx index 5d419399fcc..4739f2e526b 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewerVisualization.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewerVisualization.tsx @@ -1,16 +1,18 @@ -import React, { useCallback, useState } from 'react'; import { css } from '@emotion/css'; +import React, { useCallback, useState } from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; + import { DataSourceInstanceSettings, DateTime, dateTime, GrafanaTheme2, PanelData, urlUtil } from '@grafana/data'; import { config, getDataSourceSrv, PanelRenderer } from '@grafana/runtime'; import { Alert, CodeEditor, DateTimePicker, LinkButton, useStyles2, useTheme2 } from '@grafana/ui'; import { isExpressionQuery } from 'app/features/expressions/guards'; import { PanelOptions } from 'app/plugins/panel/table/models.gen'; +import { AccessControlAction } from 'app/types'; import { AlertQuery } from 'app/types/unified-alerting-dto'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { PanelPluginsButtonGroup, SupportedPanelPlugins } from '../PanelPluginsButtonGroup'; + import { TABLE, TIMESERIES } from '../../utils/constants'; import { Authorize } from '../Authorize'; -import { AccessControlAction } from 'app/types'; +import { PanelPluginsButtonGroup, SupportedPanelPlugins } from '../PanelPluginsButtonGroup'; type RuleViewerVisualizationProps = { data?: PanelData; diff --git a/public/app/features/alerting/unified/components/rules/ActionButton.tsx b/public/app/features/alerting/unified/components/rules/ActionButton.tsx index e87de470c54..d1650a92e25 100644 --- a/public/app/features/alerting/unified/components/rules/ActionButton.tsx +++ b/public/app/features/alerting/unified/components/rules/ActionButton.tsx @@ -1,8 +1,9 @@ -import { Button, ButtonProps } from '@grafana/ui/src/components/Button'; -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { useStyles } from '@grafana/ui'; +import { Button, ButtonProps } from '@grafana/ui/src/components/Button'; type Props = Omit; diff --git a/public/app/features/alerting/unified/components/rules/ActionIcon.tsx b/public/app/features/alerting/unified/components/rules/ActionIcon.tsx index e8000c98a88..8eb72d2c768 100644 --- a/public/app/features/alerting/unified/components/rules/ActionIcon.tsx +++ b/public/app/features/alerting/unified/components/rules/ActionIcon.tsx @@ -1,6 +1,7 @@ +import React, { FC } from 'react'; + import { IconName, Tooltip, LinkButton, Button } from '@grafana/ui'; import { PopoverContent, TooltipPlacement } from '@grafana/ui/src/components/Tooltip'; -import React, { FC } from 'react'; interface Props { tooltip: PopoverContent; diff --git a/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx b/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx index 6d4443da42d..fafd89a7466 100644 --- a/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx @@ -1,5 +1,7 @@ -import { Alert } from 'app/types/unified-alerting'; import React, { FC } from 'react'; + +import { Alert } from 'app/types/unified-alerting'; + import { AnnotationDetailsField } from '../AnnotationDetailsField'; import { DetailsField } from '../DetailsField'; diff --git a/public/app/features/alerting/unified/components/rules/AlertInstanceStateFilter.tsx b/public/app/features/alerting/unified/components/rules/AlertInstanceStateFilter.tsx index c1152853582..4cbc939d434 100644 --- a/public/app/features/alerting/unified/components/rules/AlertInstanceStateFilter.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertInstanceStateFilter.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { RadioButtonGroup, Label } from '@grafana/ui'; import { GrafanaAlertState } from 'app/types/unified-alerting-dto'; diff --git a/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx b/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx index cccace4b931..d3350d95877 100644 --- a/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx @@ -1,12 +1,15 @@ -import { GrafanaTheme2 } from '@grafana/data'; -import { Alert } from 'app/types/unified-alerting'; import { css } from '@emotion/css'; import React, { FC, useMemo } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Alert } from 'app/types/unified-alerting'; + import { alertInstanceKey } from '../../utils/rules'; import { AlertLabels } from '../AlertLabels'; +import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; + import { AlertInstanceDetails } from './AlertInstanceDetails'; import { AlertStateTag } from './AlertStateTag'; -import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; interface Props { instances: Alert[]; diff --git a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx index efeaa6596dd..b8b9127cd3e 100644 --- a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx @@ -1,6 +1,8 @@ +import React, { FC } from 'react'; + import { AlertState } from '@grafana/data'; import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; -import React, { FC } from 'react'; + import { alertStateToReadable, alertStateToState } from '../../utils/rules'; import { StateTag } from '../StateTag'; interface Props { diff --git a/public/app/features/alerting/unified/components/rules/CloudRules.tsx b/public/app/features/alerting/unified/components/rules/CloudRules.tsx index b5015337798..1e797baac95 100644 --- a/public/app/features/alerting/unified/components/rules/CloudRules.tsx +++ b/public/app/features/alerting/unified/components/rules/CloudRules.tsx @@ -1,12 +1,15 @@ import { css } from '@emotion/css'; +import pluralize from 'pluralize'; +import React, { FC, useMemo } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { LoadingPlaceholder, useStyles } from '@grafana/ui'; -import React, { FC, useMemo } from 'react'; -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { RulesGroup } from './RulesGroup'; -import { getRulesDataSources, getRulesSourceName } from '../../utils/datasource'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; -import pluralize from 'pluralize'; + +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { getRulesDataSources, getRulesSourceName } from '../../utils/datasource'; + +import { RulesGroup } from './RulesGroup'; interface Props { namespaces: CombinedRuleNamespace[]; diff --git a/public/app/features/alerting/unified/components/rules/EditCloudGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditCloudGroupModal.tsx index 52c86b0397f..8018fef98bb 100644 --- a/public/app/features/alerting/unified/components/rules/EditCloudGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/EditCloudGroupModal.tsx @@ -1,14 +1,16 @@ -import React, { useEffect, useMemo } from 'react'; -import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; -import { Modal, Button, Form, Field, Input, useStyles2 } from '@grafana/ui'; -import { durationValidationPattern } from '../../utils/time'; import { css } from '@emotion/css'; +import React, { useEffect, useMemo } from 'react'; import { useDispatch } from 'react-redux'; + +import { Modal, Button, Form, Field, Input, useStyles2 } from '@grafana/ui'; +import { useCleanup } from 'app/core/hooks/useCleanup'; +import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; + +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { updateLotexNamespaceAndGroupAction } from '../../state/actions'; import { getRulesSourceName } from '../../utils/datasource'; -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { initialAsyncRequestState } from '../../utils/redux'; -import { useCleanup } from 'app/core/hooks/useCleanup'; +import { durationValidationPattern } from '../../utils/time'; interface Props { namespace: CombinedRuleNamespace; diff --git a/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx b/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx index eb4e4262f16..99e26bf74ff 100644 --- a/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx +++ b/public/app/features/alerting/unified/components/rules/GrafanaRules.tsx @@ -1,13 +1,18 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { LoadingPlaceholder, useStyles } from '@grafana/ui'; -import React, { FC } from 'react'; -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { RulesGroup } from './RulesGroup'; -import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; + +import { flattenGrafanaManagedRules } from '../../hooks/useCombinedRuleNamespaces'; +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { initialAsyncRequestState } from '../../utils/redux'; +import { RulesGroup } from './RulesGroup'; + interface Props { namespaces: CombinedRuleNamespace[]; expandAll: boolean; @@ -15,10 +20,15 @@ interface Props { export const GrafanaRules: FC = ({ namespaces, expandAll }) => { const styles = useStyles(getStyles); + const [queryParams] = useQueryParams(); + const { loading } = useUnifiedAlertingSelector( (state) => state.promRules[GRAFANA_RULES_SOURCE_NAME] || initialAsyncRequestState ); + const wantsGroupedView = queryParams['view'] === 'grouped'; + const namespacesFormat = wantsGroupedView ? namespaces : flattenGrafanaManagedRules(namespaces); + return (
@@ -26,7 +36,7 @@ export const GrafanaRules: FC = ({ namespaces, expandAll }) => { {loading ? :
}
- {namespaces?.map((namespace) => + {namespacesFormat?.map((namespace) => namespace.groups.map((group) => ( = ({ namespaces, expandAll }) => { /> )) )} - {namespaces?.length === 0 &&

No rules found.

} + {namespacesFormat?.length === 0 &&

No rules found.

}
); }; diff --git a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx index 2a74d453395..b90632070f1 100644 --- a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx +++ b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx @@ -1,10 +1,14 @@ -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import { contextSrv } from 'app/core/services/context_srv'; import React, { FC } from 'react'; + import { CallToActionCard } from '@grafana/ui'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; + +import { useRulesAccess } from '../../utils/accessControlHooks'; export const NoRulesSplash: FC = () => { - if (contextSrv.hasEditPermissionInFolders || contextSrv.isEditor) { + const { canCreateGrafanaRules, canCreateCloudRules } = useRulesAccess(); + + if (canCreateGrafanaRules || canCreateCloudRules) { return ( { +describe('RuleDetails RBAC', () => { describe('Grafana rules action buttons', () => { const grafanaRule = getGrafanaRule({ name: 'Grafana' }); it('Should not render Edit button for users without the update permission', () => { diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index db8a5fc8e8c..74d378d52ca 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -1,10 +1,13 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { CombinedRule } from 'app/types/unified-alerting'; -import React, { FC } from 'react'; + import { AlertLabels } from '../AlertLabels'; import { DetailsField } from '../DetailsField'; + import { RuleDetailsActionButtons } from './RuleDetailsActionButtons'; import { RuleDetailsAnnotations } from './RuleDetailsAnnotations'; import { RuleDetailsDataSources } from './RuleDetailsDataSources'; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx index 43604a68733..9c52cdd2755 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx @@ -1,4 +1,8 @@ import { css } from '@emotion/css'; +import React, { FC, Fragment, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useLocation } from 'react-router-dom'; + import { GrafanaTheme2, urlUtil } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Button, ClipboardButton, ConfirmModal, HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui'; @@ -7,9 +11,7 @@ import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; import { RulerGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; -import React, { FC, Fragment, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useLocation } from 'react-router-dom'; + import { useIsRuleEditable } from '../../hooks/useIsRuleEditable'; import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; import { deleteRuleAction } from '../../state/actions'; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx index 3963d88cc51..7c9691eeac3 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { useStyles2 } from '@grafana/ui'; + import { AnnotationDetailsField } from '../AnnotationDetailsField'; type Props = { diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx index fc6584d9d24..39facac253a 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsDataSources.tsx @@ -1,10 +1,12 @@ import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; -import React, { useMemo } from 'react'; + import { isCloudRulesSource } from '../../utils/datasource'; import { isGrafanaRulerRule } from '../../utils/rules'; import { DetailsField } from '../DetailsField'; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsExpression.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsExpression.tsx index af3750a7bed..ea5cd7969cc 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsExpression.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsExpression.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; + import { isCloudRulesSource } from '../../utils/datasource'; import { DetailsField } from '../DetailsField'; import { Expression } from '../Expression'; diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx index a7ca0178276..b0cd5ecfc0f 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx @@ -1,5 +1,7 @@ -import { CombinedRuleGroup } from 'app/types/unified-alerting'; import React, { FC } from 'react'; + +import { CombinedRuleGroup } from 'app/types/unified-alerting'; + import { DetailsField } from '../DetailsField'; interface Props { diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx index 80efbc627eb..c214c4a4e4b 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx @@ -1,17 +1,20 @@ -import { Alert, Rule } from 'app/types/unified-alerting'; +import { css, cx } from '@emotion/css'; import React, { useMemo, useState } from 'react'; -import { isAlertingRule } from '../../utils/rules'; -import { DetailsField } from '../DetailsField'; -import { AlertInstancesTable } from './AlertInstancesTable'; -import { SortOrder } from 'app/plugins/panel/alertlist/types'; -import { GrafanaAlertState } from 'app/types/unified-alerting-dto'; + import { GrafanaTheme } from '@grafana/data'; import { useStyles } from '@grafana/ui'; -import { css, cx } from '@emotion/css'; -import { labelsMatchMatchers, parseMatchers } from 'app/features/alerting/unified/utils/alertmanager'; -import { sortAlerts } from 'app/features/alerting/unified/utils/misc'; import { MatcherFilter } from 'app/features/alerting/unified/components/alert-groups/MatcherFilter'; import { AlertInstanceStateFilter } from 'app/features/alerting/unified/components/rules/AlertInstanceStateFilter'; +import { labelsMatchMatchers, parseMatchers } from 'app/features/alerting/unified/utils/alertmanager'; +import { sortAlerts } from 'app/features/alerting/unified/utils/misc'; +import { SortOrder } from 'app/plugins/panel/alertlist/types'; +import { Alert, Rule } from 'app/types/unified-alerting'; +import { GrafanaAlertState } from 'app/types/unified-alerting-dto'; + +import { isAlertingRule } from '../../utils/rules'; +import { DetailsField } from '../DetailsField'; + +import { AlertInstancesTable } from './AlertInstancesTable'; type Props = { promRule?: Rule; diff --git a/public/app/features/alerting/unified/components/rules/RuleHealth.tsx b/public/app/features/alerting/unified/components/rules/RuleHealth.tsx index 3cb4c3d9b15..61cd9f346b7 100644 --- a/public/app/features/alerting/unified/components/rules/RuleHealth.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleHealth.tsx @@ -1,8 +1,9 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Icon, Tooltip, useStyles2 } from '@grafana/ui'; import { Rule } from 'app/types/unified-alerting'; -import React, { FC } from 'react'; interface Prom { rule: Rule; diff --git a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx index fa95540ad4a..2076fa92409 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListErrors.tsx @@ -1,10 +1,12 @@ import { css } from '@emotion/css'; -import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; -import { Alert, Button, Tooltip, useStyles2 } from '@grafana/ui'; import { SerializedError } from '@reduxjs/toolkit'; import pluralize from 'pluralize'; import React, { useMemo, ReactElement, useState, FC } from 'react'; import { useLocalStorage } from 'react-use'; + +import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; +import { Alert, Button, Tooltip, useStyles2 } from '@grafana/ui'; + import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { getRulesDataSources, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { isRulerNotSupportedResponse } from '../../utils/rules'; diff --git a/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx b/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx index e7d2192e5d8..c0c0b5a28e6 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx @@ -1,13 +1,18 @@ import { render } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; +import { byRole } from 'testing-library-selector'; + +import { locationService } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import { configureStore } from 'app/store/configureStore'; import { AccessControlAction } from 'app/types'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; -import React from 'react'; -import { Provider } from 'react-redux'; -import { byRole } from 'testing-library-selector'; + import { mockCombinedRule, mockDataSource } from '../../mocks'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; + import { RuleListGroupView } from './RuleListGroupView'; const ui = { @@ -16,7 +21,7 @@ const ui = { }; describe('RuleListGroupView', () => { - describe('FGAC', () => { + describe('RBAC', () => { jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true); it('Should display Grafana rules when the user has the alert rule read permission', () => { @@ -76,7 +81,9 @@ function renderRuleList(namespaces: CombinedRuleNamespace[]) { render( - + + + ); } diff --git a/public/app/features/alerting/unified/components/rules/RuleListGroupView.tsx b/public/app/features/alerting/unified/components/rules/RuleListGroupView.tsx index 9bb64492da7..ffd5ebc904d 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListGroupView.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListGroupView.tsx @@ -1,8 +1,11 @@ +import React, { FC, useMemo } from 'react'; + import { AccessControlAction } from 'app/types'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; -import React, { FC, useMemo } from 'react'; + import { isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource'; import { Authorize } from '../Authorize'; + import { CloudRules } from './CloudRules'; import { GrafanaRules } from './GrafanaRules'; diff --git a/public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx b/public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx index 3dfd9dc8c86..cc8aec8d859 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx @@ -1,11 +1,14 @@ import { css } from '@emotion/css'; +import React, { FC, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { CombinedRule } from 'app/types/unified-alerting'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; -import React, { FC, useState } from 'react'; + import { alertStateToReadable } from '../../utils/rules'; import { CollapseToggle } from '../CollapseToggle'; + import { RulesTable } from './RulesTable'; interface Props { diff --git a/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx b/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx index f833dbba8db..3f5242cb5ed 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListStateView.tsx @@ -1,9 +1,12 @@ +import React, { FC, useMemo } from 'react'; + import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { CombinedRule, CombinedRuleNamespace } from 'app/types/unified-alerting'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; -import React, { FC, useMemo } from 'react'; + import { getFiltersFromUrlParams } from '../../utils/misc'; import { isAlertingRule } from '../../utils/rules'; + import { RuleListStateSection } from './RuleListStateSection'; interface Props { diff --git a/public/app/features/alerting/unified/components/rules/RuleState.tsx b/public/app/features/alerting/unified/components/rules/RuleState.tsx index 85e85a2d448..08990964c69 100644 --- a/public/app/features/alerting/unified/components/rules/RuleState.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleState.tsx @@ -1,10 +1,13 @@ import { css } from '@emotion/css'; +import React, { FC, useMemo } from 'react'; + import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data'; import { HorizontalGroup, Spinner, useStyles2 } from '@grafana/ui'; import { CombinedRule } from 'app/types/unified-alerting'; import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; -import React, { FC, useMemo } from 'react'; + import { isAlertingRule, isRecordingRule, getFirstActiveAt } from '../../utils/rules'; + import { AlertStateTag } from './AlertStateTag'; interface Props { diff --git a/public/app/features/alerting/unified/components/rules/RuleStats.tsx b/public/app/features/alerting/unified/components/rules/RuleStats.tsx index 58415d364f2..f5fb8c39d97 100644 --- a/public/app/features/alerting/unified/components/rules/RuleStats.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleStats.tsx @@ -1,7 +1,9 @@ -import { CombinedRule, CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; -import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import pluralize from 'pluralize'; import React, { FC, Fragment, useMemo } from 'react'; + +import { CombinedRule, CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; + import { isAlertingRule, isRecordingRule, isRecordingRulerRule } from '../../utils/rules'; import { StateColoredText } from '../StateColoredText'; diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx index e68188b4268..766dfaa13a6 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx @@ -1,21 +1,27 @@ -import React, { FormEvent, useState } from 'react'; -import { Button, Field, Icon, Input, Label, RadioButtonGroup, Tooltip, useStyles } from '@grafana/ui'; -import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data'; import { css, cx } from '@emotion/css'; import { debounce } from 'lodash'; +import React, { FormEvent, useState } from 'react'; -import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { getFiltersFromUrlParams } from '../../utils/misc'; -import { DataSourcePicker } from '@grafana/runtime'; -import { alertStateToReadable } from '../../utils/rules'; +import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; +import { DataSourcePicker } from '@grafana/runtime'; +import { Button, Field, Icon, Input, Label, RadioButtonGroup, Tooltip, useStyles } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; + +import { getFiltersFromUrlParams } from '../../utils/misc'; +import { alertStateToReadable } from '../../utils/rules'; const ViewOptions: SelectableValue[] = [ + { + icon: 'list-ul', + label: 'List', + value: 'list', + }, { icon: 'folder', - label: 'Groups', - value: 'group', + label: 'Grouped', + value: 'grouped', }, { icon: 'heart-rate', @@ -147,7 +153,7 @@ const RulesFilter = () => {
diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx index a9eafa81ed3..f9d61ecbd61 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx @@ -1,11 +1,14 @@ import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { configureStore } from 'app/store/configureStore'; -import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; import React from 'react'; import { Provider } from 'react-redux'; import { byTestId, byText } from 'testing-library-selector'; + +import { configureStore } from 'app/store/configureStore'; +import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; + import { mockCombinedRule, mockDataSource } from '../../mocks'; + import { RulesGroup } from './RulesGroup'; const hasRulerMock = jest.fn(); @@ -95,13 +98,13 @@ describe('Rules group tests', () => { expect(ui.editGroupButton.query()).not.toBeInTheDocument(); }); - it('Delete button click should display confirmation modal', () => { + it('Delete button click should display confirmation modal', async () => { // Arrange hasRulerMock.mockReturnValue(true); // Act renderRulesGroup(namespace, group); - userEvent.click(ui.deleteGroupButton.get()); + await userEvent.click(ui.deleteGroupButton.get()); // Assert expect(ui.confirmDeleteModal.header.get()).toBeInTheDocument(); diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index ef668398c0c..d87303d3760 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -1,23 +1,27 @@ import { css } from '@emotion/css'; +import pluralize from 'pluralize'; +import React, { FC, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; + import { GrafanaTheme2 } from '@grafana/data'; import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; import kbn from 'app/core/utils/kbn'; import { AccessControlAction } from 'app/types'; import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; -import pluralize from 'pluralize'; -import React, { FC, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; + import { useFolder } from '../../hooks/useFolder'; import { useHasRuler } from '../../hooks/useHasRuler'; import { deleteRulesGroupAction } from '../../state/actions'; import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource'; import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; import { CollapseToggle } from '../CollapseToggle'; +import { RuleLocation } from '../RuleLocation'; + import { ActionIcon } from './ActionIcon'; import { EditCloudGroupModal } from './EditCloudGroupModal'; -import { RulesTable } from './RulesTable'; import { RuleStats } from './RuleStats'; +import { RulesTable } from './RulesTable'; interface Props { namespace: CombinedRuleNamespace; @@ -118,7 +122,13 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll } ); } - const groupName = isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name; + // ungrouped rules are rules that are in the "default" group name + const isUngrouped = group.name === 'default'; + const groupName = isUngrouped ? ( + + ) : ( + + ); return (
diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index 221d238b91d..e6423e96dc1 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -1,16 +1,19 @@ +import { css, cx } from '@emotion/css'; +import React, { FC, useMemo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import React, { FC, useMemo } from 'react'; -import { css, cx } from '@emotion/css'; -import { RuleDetails } from './RuleDetails'; -import { isCloudRulesSource } from '../../utils/datasource'; -import { useHasRuler } from '../../hooks/useHasRuler'; import { CombinedRule } from 'app/types/unified-alerting'; + +import { useHasRuler } from '../../hooks/useHasRuler'; import { Annotation } from '../../utils/constants'; -import { RuleState } from './RuleState'; -import { RuleHealth } from './RuleHealth'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines'; +import { RuleLocation } from '../RuleLocation'; + +import { RuleDetails } from './RuleDetails'; +import { RuleHealth } from './RuleHealth'; +import { RuleState } from './RuleState'; type RuleTableColumnProps = DynamicTableColumnProps; type RuleTableItemProps = DynamicTableItemProps; @@ -137,8 +140,15 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) { // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { const { namespace, group } = rule; - const { rulesSource } = namespace; - return isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name; + // ungrouped rules are rules that are in the "default" group name + const isUngrouped = group.name === 'default'; + const groupName = isUngrouped ? ( + + ) : ( + + ); + + return groupName; }, size: 5, }); diff --git a/public/app/features/alerting/unified/components/rules/StateHistory.tsx b/public/app/features/alerting/unified/components/rules/StateHistory.tsx index f7050eb198c..49c53d93046 100644 --- a/public/app/features/alerting/unified/components/rules/StateHistory.tsx +++ b/public/app/features/alerting/unified/components/rules/StateHistory.tsx @@ -1,14 +1,17 @@ -import React, { FC } from 'react'; +import { css } from '@emotion/css'; import { uniqueId } from 'lodash'; +import React, { FC } from 'react'; + import { AlertState, dateTimeFormat, GrafanaTheme } from '@grafana/data'; import { Alert, LoadingPlaceholder, useStyles } from '@grafana/ui'; -import { css } from '@emotion/css'; import { StateHistoryItem, StateHistoryItemData } from 'app/types/unified-alerting'; -import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; -import { AlertStateTag } from './AlertStateTag'; +import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; + import { useManagedAlertStateHistory } from '../../hooks/useManagedAlertStateHistory'; import { AlertLabel } from '../AlertLabel'; -import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; +import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; + +import { AlertStateTag } from './AlertStateTag'; type StateHistoryRowItem = { id: string; diff --git a/public/app/features/alerting/unified/components/silences/AmAlertStateTag.tsx b/public/app/features/alerting/unified/components/silences/AmAlertStateTag.tsx index 66206ed07ad..96493b0b5ec 100644 --- a/public/app/features/alerting/unified/components/silences/AmAlertStateTag.tsx +++ b/public/app/features/alerting/unified/components/silences/AmAlertStateTag.tsx @@ -1,5 +1,7 @@ -import { AlertState } from 'app/plugins/datasource/alertmanager/types'; import React, { FC } from 'react'; + +import { AlertState } from 'app/plugins/datasource/alertmanager/types'; + import { State, StateTag } from '../StateTag'; const alertStateToState: Record = { diff --git a/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx b/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx index d464202f3a4..d28d9ece726 100644 --- a/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx +++ b/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx @@ -1,19 +1,21 @@ +import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; import { useFormContext } from 'react-hook-form'; -import { useDebounce } from 'react-use'; import { useDispatch } from 'react-redux'; -import { css } from '@emotion/css'; +import { useDebounce } from 'react-use'; + import { dateTime, GrafanaTheme2 } from '@grafana/data'; import { Badge, useStyles2 } from '@grafana/ui'; -import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; -import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces'; -import { findAlertInstancesWithMatchers } from '../../utils/matchers'; -import { fetchAllPromAndRulerRulesAction } from '../../state/actions'; import { Alert, AlertingRule } from 'app/types/unified-alerting'; + +import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces'; +import { fetchAllPromAndRulerRulesAction } from '../../state/actions'; import { MatcherFieldValue, SilenceFormFields } from '../../types/silence-form'; +import { findAlertInstancesWithMatchers } from '../../utils/matchers'; import { isAlertingRule } from '../../utils/rules'; -import { AlertStateTag } from '../rules/AlertStateTag'; import { AlertLabels } from '../AlertLabels'; +import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; +import { AlertStateTag } from '../rules/AlertStateTag'; type MatchedRulesTableItemProps = DynamicTableItemProps<{ matchedInstance: Alert; diff --git a/public/app/features/alerting/unified/components/silences/Matchers.tsx b/public/app/features/alerting/unified/components/silences/Matchers.tsx index 0d686d7027f..f5b9674817e 100644 --- a/public/app/features/alerting/unified/components/silences/Matchers.tsx +++ b/public/app/features/alerting/unified/components/silences/Matchers.tsx @@ -1,7 +1,9 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { TagList, useStyles2 } from '@grafana/ui'; import { Matcher } from 'app/plugins/datasource/alertmanager/types'; + import { matcherToOperator } from '../../utils/alertmanager'; type MatchersProps = { matchers: Matcher[] }; diff --git a/public/app/features/alerting/unified/components/silences/MatchersField.tsx b/public/app/features/alerting/unified/components/silences/MatchersField.tsx index 7464af9ef2f..4ccf213132e 100644 --- a/public/app/features/alerting/unified/components/silences/MatchersField.tsx +++ b/public/app/features/alerting/unified/components/silences/MatchersField.tsx @@ -1,10 +1,12 @@ -import React, { FC } from 'react'; -import { Button, Field, Input, IconButton, InputControl, useStyles2, Select } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; import { useFormContext, useFieldArray } from 'react-hook-form'; -import { SilenceFormFields } from '../../types/silence-form'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, Field, Input, IconButton, InputControl, useStyles2, Select } from '@grafana/ui'; import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; + +import { SilenceFormFields } from '../../types/silence-form'; import { matcherFieldOptions } from '../../utils/alertmanager'; interface Props { diff --git a/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx b/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx index 95a298338c1..4c142c092a9 100644 --- a/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx +++ b/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx @@ -1,7 +1,10 @@ +import React, { FC } from 'react'; + import { CallToActionCard } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { contextSrv } from 'app/core/services/context_srv'; -import React, { FC } from 'react'; + +import { getInstancesPermissions } from '../../utils/access-control'; import { makeAMLink } from '../../utils/misc'; type Props = { @@ -9,7 +12,9 @@ type Props = { }; export const NoSilencesSplash: FC = ({ alertManagerSourceName }) => { - if (contextSrv.isEditor) { + const permissions = getInstancesPermissions(alertManagerSourceName); + + if (contextSrv.hasAccess(permissions.create, contextSrv.isEditor)) { return ( { diff --git a/public/app/features/alerting/unified/components/silences/SilenceStateTag.tsx b/public/app/features/alerting/unified/components/silences/SilenceStateTag.tsx index 4b212d3c1a1..fc36282db8c 100644 --- a/public/app/features/alerting/unified/components/silences/SilenceStateTag.tsx +++ b/public/app/features/alerting/unified/components/silences/SilenceStateTag.tsx @@ -1,5 +1,7 @@ -import { SilenceState } from 'app/plugins/datasource/alertmanager/types'; import React, { FC } from 'react'; + +import { SilenceState } from 'app/plugins/datasource/alertmanager/types'; + import { State, StateTag } from '../StateTag'; const silenceStateToState: Record = { diff --git a/public/app/features/alerting/unified/components/silences/SilenceTableRow.tsx b/public/app/features/alerting/unified/components/silences/SilenceTableRow.tsx index 7f76d3c4a03..1897361de84 100644 --- a/public/app/features/alerting/unified/components/silences/SilenceTableRow.tsx +++ b/public/app/features/alerting/unified/components/silences/SilenceTableRow.tsx @@ -1,18 +1,22 @@ -import React, { FC, Fragment, useState } from 'react'; -import { dateMath, GrafanaTheme, intervalToAbbreviatedDurationString } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { FC, Fragment, useState } from 'react'; +import { useDispatch } from 'react-redux'; + +import { dateMath, GrafanaTheme, intervalToAbbreviatedDurationString } from '@grafana/data'; +import { useStyles, Link } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; import { Silence, AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; + +import { expireSilenceAction } from '../../state/actions'; +import { makeAMLink } from '../../utils/misc'; import { CollapseToggle } from '../CollapseToggle'; import { ActionButton } from '../rules/ActionButton'; import { ActionIcon } from '../rules/ActionIcon'; -import { useStyles, Link } from '@grafana/ui'; -import SilencedAlertsTable from './SilencedAlertsTable'; -import { expireSilenceAction } from '../../state/actions'; -import { useDispatch } from 'react-redux'; + import { Matchers } from './Matchers'; import { SilenceStateTag } from './SilenceStateTag'; -import { makeAMLink } from '../../utils/misc'; -import { contextSrv } from 'app/core/services/context_srv'; +import SilencedAlertsTable from './SilencedAlertsTable'; + interface Props { className?: string; silence: Silence; diff --git a/public/app/features/alerting/unified/components/silences/SilencedAlertsTable.tsx b/public/app/features/alerting/unified/components/silences/SilencedAlertsTable.tsx index 14b08b8dc8f..94b0d802d3b 100644 --- a/public/app/features/alerting/unified/components/silences/SilencedAlertsTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencedAlertsTable.tsx @@ -1,10 +1,13 @@ -import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; -import React, { FC } from 'react'; -import { getAlertTableStyles } from '../../styles/table'; -import { useStyles2 } from '@grafana/ui'; -import { SilencedAlertsTableRow } from './SilencedAlertsTableRow'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; +import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; + +import { getAlertTableStyles } from '../../styles/table'; + +import { SilencedAlertsTableRow } from './SilencedAlertsTableRow'; interface Props { silencedAlerts: AlertmanagerAlert[]; diff --git a/public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx b/public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx index 3b14a25436b..8396e48c3f9 100644 --- a/public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx @@ -1,8 +1,11 @@ -import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; import React, { FC, useState } from 'react'; -import { CollapseToggle } from '../CollapseToggle'; + import { intervalToAbbreviatedDurationString } from '@grafana/data'; +import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; + import { AlertLabels } from '../AlertLabels'; +import { CollapseToggle } from '../CollapseToggle'; + import { AmAlertStateTag } from './AmAlertStateTag'; interface Props { diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index dd2bcdadf64..75aac08edf2 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -1,6 +1,10 @@ -import { MatcherOperator, Silence, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types'; +import { css, cx } from '@emotion/css'; +import { pickBy } from 'lodash'; import React, { FC, useMemo, useState } from 'react'; -import { Button, Field, FieldSet, Input, LinkButton, TextArea, useStyles2 } from '@grafana/ui'; +import { useForm, FormProvider } from 'react-hook-form'; +import { useDispatch } from 'react-redux'; +import { useDebounce } from 'react-use'; + import { DefaultTimeZone, parseDuration, @@ -10,23 +14,22 @@ import { isValidDate, GrafanaTheme2, } from '@grafana/data'; -import { useDebounce } from 'react-use'; import { config } from '@grafana/runtime'; -import { pickBy } from 'lodash'; -import MatchersField from './MatchersField'; -import { MatchedSilencedRules } from './MatchedSilencedRules'; -import { useForm, FormProvider } from 'react-hook-form'; -import { SilenceFormFields } from '../../types/silence-form'; -import { useDispatch } from 'react-redux'; -import { createOrUpdateSilenceAction } from '../../state/actions'; -import { SilencePeriod } from './SilencePeriod'; -import { css, cx } from '@emotion/css'; -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { makeAMLink } from '../../utils/misc'; +import { Button, Field, FieldSet, Input, LinkButton, TextArea, useStyles2 } from '@grafana/ui'; import { useCleanup } from 'app/core/hooks/useCleanup'; -import { parseQueryParamMatchers } from '../../utils/matchers'; -import { matcherToMatcherField, matcherFieldToMatcher } from '../../utils/alertmanager'; +import { MatcherOperator, Silence, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types'; + import { useURLSearchParams } from '../../hooks/useURLSearchParams'; +import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; +import { createOrUpdateSilenceAction } from '../../state/actions'; +import { SilenceFormFields } from '../../types/silence-form'; +import { matcherToMatcherField, matcherFieldToMatcher } from '../../utils/alertmanager'; +import { parseQueryParamMatchers } from '../../utils/matchers'; +import { makeAMLink } from '../../utils/misc'; + +import { MatchedSilencedRules } from './MatchedSilencedRules'; +import MatchersField from './MatchersField'; +import { SilencePeriod } from './SilencePeriod'; interface Props { silence?: Silence; diff --git a/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx b/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx index 377a06bc63d..2d926a9fe41 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesFilter.tsx @@ -1,13 +1,15 @@ -import React, { FormEvent, useState } from 'react'; import { css } from '@emotion/css'; -import { Label, Icon, Input, Tooltip, RadioButtonGroup, useStyles2, Button, Field } from '@grafana/ui'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { getSilenceFiltersFromUrlParams } from '../../utils/misc'; -import { SilenceState } from 'app/plugins/datasource/alertmanager/types'; -import { parseMatchers } from '../../utils/alertmanager'; import { debounce, uniqueId } from 'lodash'; +import React, { FormEvent, useState } from 'react'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; +import { Label, Icon, Input, Tooltip, RadioButtonGroup, useStyles2, Button, Field } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { SilenceState } from 'app/plugins/datasource/alertmanager/types'; + +import { parseMatchers } from '../../utils/alertmanager'; +import { getSilenceFiltersFromUrlParams } from '../../utils/misc'; const stateOptions: SelectableValue[] = Object.entries(SilenceState).map(([key, value]) => ({ label: key, diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx index b70fd472166..9e7d5b44ac3 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx @@ -1,25 +1,28 @@ -import React, { FC, useMemo } from 'react'; -import { GrafanaTheme2, dateMath } from '@grafana/data'; -import { Icon, useStyles2, Link, Button } from '@grafana/ui'; import { css } from '@emotion/css'; -import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types'; -import { NoSilencesSplash } from './NoSilencesCTA'; -import { getSilenceFiltersFromUrlParams, makeAMLink } from '../../utils/misc'; -import { contextSrv } from 'app/core/services/context_srv'; +import React, { FC, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; + +import { GrafanaTheme2, dateMath } from '@grafana/data'; +import { Stack } from '@grafana/experimental'; +import { Icon, useStyles2, Link, Button } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { SilencesFilter } from './SilencesFilter'; +import { contextSrv } from 'app/core/services/context_srv'; +import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types'; + +import { expireSilenceAction } from '../../state/actions'; +import { getInstancesPermissions } from '../../utils/access-control'; import { parseMatchers } from '../../utils/alertmanager'; +import { getSilenceFiltersFromUrlParams, makeAMLink } from '../../utils/misc'; +import { Authorize } from '../Authorize'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; -import { SilenceStateTag } from './SilenceStateTag'; -import { Matchers } from './Matchers'; import { ActionButton } from '../rules/ActionButton'; import { ActionIcon } from '../rules/ActionIcon'; -import { useDispatch } from 'react-redux'; -import { expireSilenceAction } from '../../state/actions'; + +import { Matchers } from './Matchers'; +import { NoSilencesSplash } from './NoSilencesCTA'; import { SilenceDetails } from './SilenceDetails'; -import { Stack } from '@grafana/experimental'; -import { Authorize } from '../Authorize'; -import { getInstancesPermissions } from '../../utils/access-control'; +import { SilenceStateTag } from './SilenceStateTag'; +import { SilencesFilter } from './SilencesFilter'; export interface SilenceTableItem extends Silence { silencedAlerts: AlertmanagerAlert[]; diff --git a/public/app/features/alerting/unified/hooks/useAlertManagerSourceName.ts b/public/app/features/alerting/unified/hooks/useAlertManagerSourceName.ts index 9ffbcd069f4..8fe2c34a600 100644 --- a/public/app/features/alerting/unified/hooks/useAlertManagerSourceName.ts +++ b/public/app/features/alerting/unified/hooks/useAlertManagerSourceName.ts @@ -1,6 +1,8 @@ +import { useCallback } from 'react'; + import { useQueryParams } from 'app/core/hooks/useQueryParams'; import store from 'app/core/store'; -import { useCallback } from 'react'; + import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants'; import { getAlertManagerDataSources, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; diff --git a/public/app/features/alerting/unified/hooks/useAlertQueriesStatus.ts b/public/app/features/alerting/unified/hooks/useAlertQueriesStatus.ts index b5e3c61eb05..4a7b3386a92 100644 --- a/public/app/features/alerting/unified/hooks/useAlertQueriesStatus.ts +++ b/public/app/features/alerting/unified/hooks/useAlertQueriesStatus.ts @@ -1,6 +1,7 @@ +import { useMemo } from 'react'; + import { getDataSourceSrv } from '@grafana/runtime'; import { AlertQuery } from 'app/types/unified-alerting-dto'; -import { useMemo } from 'react'; export function useAlertQueriesStatus(queries: AlertQuery[]) { const allDataSourcesAvailable = useMemo( diff --git a/public/app/features/alerting/unified/hooks/useCombinedRule.ts b/public/app/features/alerting/unified/hooks/useCombinedRule.ts index 79c517592dc..55d853abcc2 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRule.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRule.ts @@ -1,12 +1,15 @@ -import { CombinedRule, RuleIdentifier, RuleNamespace } from 'app/types/unified-alerting'; -import { RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; import { useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { useAsync } from 'react-use'; + +import { CombinedRule, RuleIdentifier, RuleNamespace } from 'app/types/unified-alerting'; +import { RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; + import { fetchPromAndRulerRulesAction } from '../state/actions'; import { AsyncRequestMapSlice, AsyncRequestState, initialAsyncRequestState } from '../utils/redux'; import * as ruleId from '../utils/rule-id'; import { isRulerNotSupportedResponse } from '../utils/rules'; + import { useCombinedRuleNamespaces } from './useCombinedRuleNamespaces'; import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.test.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.test.ts new file mode 100644 index 00000000000..544b2837855 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.test.ts @@ -0,0 +1,58 @@ +import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; + +import { sortRulesByName, flattenGrafanaManagedRules } from './useCombinedRuleNamespaces'; + +describe('flattenGrafanaManagedRules', () => { + it('should properly transform grafana managed namespaces', () => { + // the rules from both ungrouped groups should go in the default group + const ungroupedGroup1 = { + name: 'my-rule', + rules: [{ name: 'my-rule' }], + } as CombinedRuleGroup; + + const ungroupedGroup2 = { + name: 'another-rule', + rules: [{ name: 'another-rule' }], + } as CombinedRuleGroup; + + // the rules from both these groups should go in their own group name + const group1 = { + name: 'group1', + rules: [{ name: 'rule-1' }, { name: 'rule-2' }], + } as CombinedRuleGroup; + + const group2 = { + name: 'group2', + rules: [{ name: 'rule-1' }, { name: 'rule-2' }], + } as CombinedRuleGroup; + + const namespace1 = { + rulesSource: 'grafana', + name: 'ns1', + groups: [ungroupedGroup1, ungroupedGroup2, group1, group2], + }; + + const namespace2 = { + rulesSource: 'grafana', + name: 'ns2', + groups: [ungroupedGroup1], + }; + + const input = [namespace1, namespace2] as CombinedRuleNamespace[]; + const [ns1, ns2] = flattenGrafanaManagedRules(input); + + expect(ns1.groups).toEqual([ + { + name: 'default', + rules: sortRulesByName([...ungroupedGroup1.rules, ...ungroupedGroup2.rules, ...group1.rules, ...group2.rules]), + }, + ]); + + expect(ns2.groups).toEqual([ + { + name: 'default', + rules: ungroupedGroup1.rules, + }, + ]); + }); +}); diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts index 259ff2af8c3..127569f59de 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts @@ -1,3 +1,5 @@ +import { useMemo, useRef } from 'react'; + import { CombinedRule, CombinedRuleGroup, @@ -8,7 +10,7 @@ import { RulesSource, } from 'app/types/unified-alerting'; import { RulerRuleDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; -import { useMemo, useRef } from 'react'; + import { getAllRulesSources, getRulesSourceByName, @@ -16,6 +18,7 @@ import { isGrafanaRulesSource, } from '../utils/datasource'; import { isAlertingRule, isAlertingRulerRule, isRecordingRulerRule } from '../utils/rules'; + import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; interface CacheValue { @@ -81,17 +84,7 @@ export function useCombinedRuleNamespaces(rulesSourceName?: string): CombinedRul }); const result = Object.values(namespaces); - if (isGrafanaRulesSource(rulesSource)) { - // merge all groups in case of grafana managed, essentially treating namespaces (folders) as gorups - result.forEach((namespace) => { - namespace.groups = [ - { - name: 'default', - rules: namespace.groups.flatMap((g) => g.rules).sort((a, b) => a.name.localeCompare(b.name)), - }, - ]; - }); - } + cache.current[rulesSourceName] = { promRules, rulerRules, result }; return result; }) @@ -100,6 +93,28 @@ export function useCombinedRuleNamespaces(rulesSourceName?: string): CombinedRul ); } +// merge all groups in case of grafana managed, essentially treating namespaces (folders) as groups +export function flattenGrafanaManagedRules(namespaces: CombinedRuleNamespace[]) { + return namespaces.map((namespace) => { + const newNamespace: CombinedRuleNamespace = { + ...namespace, + groups: [], + }; + + // add default group with ungrouped rules + newNamespace.groups.push({ + name: 'default', + rules: sortRulesByName(namespace.groups.flatMap((group) => group.rules)), + }); + + return newNamespace; + }); +} + +export function sortRulesByName(rules: CombinedRule[]) { + return rules.sort((a, b) => a.name.localeCompare(b.name)); +} + function addRulerGroupsToCombinedNamespace(namespace: CombinedRuleNamespace, groups: RulerRuleGroupDTO[]): void { namespace.groups = groups.map((group) => { const combinedGroup: CombinedRuleGroup = { diff --git a/public/app/features/alerting/unified/hooks/useControlledFieldArray.ts b/public/app/features/alerting/unified/hooks/useControlledFieldArray.ts index b42831c0c36..3853c99140f 100644 --- a/public/app/features/alerting/unified/hooks/useControlledFieldArray.ts +++ b/public/app/features/alerting/unified/hooks/useControlledFieldArray.ts @@ -1,8 +1,7 @@ +import { set } from 'lodash'; import { useCallback } from 'react'; import { UseFormReturn } from 'react-hook-form'; -import { set } from 'lodash'; - interface Options { name: string; formAPI: UseFormReturn; diff --git a/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.ts b/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.ts index 823141fec9d..bf613686bf1 100644 --- a/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.ts +++ b/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.ts @@ -1,4 +1,5 @@ import * as reactRedux from 'react-redux'; + import { useExternalAmSelector } from './useExternalAmSelector'; const createMockStoreState = ( diff --git a/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts b/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts index 9bc63239fcb..0a87417fefc 100644 --- a/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts +++ b/public/app/features/alerting/unified/hooks/useExternalAmSelector.ts @@ -1,4 +1,5 @@ import { useSelector } from 'react-redux'; + import { StoreState } from '../../../../types'; const SUFFIX_REGEX = /\/api\/v[1|2]\/alerts/i; diff --git a/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts b/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts index a47e9685979..3aa49042d93 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredAmGroups.ts @@ -1,6 +1,8 @@ +import { useMemo } from 'react'; + import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; -import { useMemo } from 'react'; + import { labelsMatchMatchers, parseMatchers } from '../utils/alertmanager'; import { getFiltersFromUrlParams } from '../utils/misc'; diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts index b8be1dd1edb..20cf18caf17 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts @@ -1,13 +1,14 @@ import { useMemo } from 'react'; -import { CombinedRuleGroup, CombinedRuleNamespace, FilterState } from 'app/types/unified-alerting'; -import { isCloudRulesSource } from '../utils/datasource'; -import { isAlertingRule, isGrafanaRulerRule } from '../utils/rules'; -import { getFiltersFromUrlParams } from '../utils/misc'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; import { getDataSourceSrv } from '@grafana/runtime'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { CombinedRuleGroup, CombinedRuleNamespace, FilterState } from 'app/types/unified-alerting'; +import { PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; + import { labelsMatchMatchers, parseMatchers } from '../utils/alertmanager'; +import { isCloudRulesSource } from '../utils/datasource'; +import { getFiltersFromUrlParams } from '../utils/misc'; +import { isAlertingRule, isGrafanaRulerRule } from '../utils/rules'; export const useFilteredRules = (namespaces: CombinedRuleNamespace[]) => { const [queryParams] = useQueryParams(); diff --git a/public/app/features/alerting/unified/hooks/useFolder.ts b/public/app/features/alerting/unified/hooks/useFolder.ts index 196c0406456..a02710b026b 100644 --- a/public/app/features/alerting/unified/hooks/useFolder.ts +++ b/public/app/features/alerting/unified/hooks/useFolder.ts @@ -1,10 +1,13 @@ -import { FolderDTO } from 'app/types'; -import { useDispatch } from 'react-redux'; -import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; + +import { FolderDTO } from 'app/types'; + import { fetchFolderIfNotFetchedAction } from '../state/actions'; import { initialAsyncRequestState } from '../utils/redux'; +import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; + interface ReturnBag { folder?: FolderDTO; loading: boolean; diff --git a/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts b/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts index 72932587d2a..1390f21b523 100644 --- a/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts +++ b/public/app/features/alerting/unified/hooks/useGroupedAlerts.ts @@ -1,7 +1,8 @@ -import { useMemo } from 'react'; -import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; -import { Labels } from '@grafana/data'; import { uniqBy } from 'lodash'; +import { useMemo } from 'react'; + +import { Labels } from '@grafana/data'; +import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; export const useGroupedAlerts = (groups: AlertmanagerGroup[], groupBy: string[]): AlertmanagerGroup[] => { return useMemo(() => { diff --git a/public/app/features/alerting/unified/hooks/useHasRuler.ts b/public/app/features/alerting/unified/hooks/useHasRuler.ts index eb326280c64..fb4d2e5960c 100644 --- a/public/app/features/alerting/unified/hooks/useHasRuler.ts +++ b/public/app/features/alerting/unified/hooks/useHasRuler.ts @@ -1,6 +1,9 @@ -import { RulesSource } from 'app/types/unified-alerting'; import { useCallback } from 'react'; + +import { RulesSource } from 'app/types/unified-alerting'; + import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; + import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; // datasource has ruler if it's grafana managed or if we're able to load rules from it diff --git a/public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx b/public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx index 7925df6f9d6..8c2e2868787 100644 --- a/public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx +++ b/public/app/features/alerting/unified/hooks/useIsRuleEditable.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; +import { Provider } from 'react-redux'; + import { contextSrv } from 'app/core/services/context_srv'; import { configureStore } from 'app/store/configureStore'; import { AccessControlAction, FolderDTO, StoreState } from 'app/types'; -import { Provider } from 'react-redux'; + import { mockFolder, mockRulerAlertingRule, mockRulerGrafanaRule } from '../mocks'; + import { useFolder } from './useFolder'; import { useIsRuleEditable } from './useIsRuleEditable'; import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; @@ -17,7 +20,7 @@ const mocks = { }; describe('useIsRuleEditable', () => { - describe('FGAC enabled', () => { + describe('RBAC enabled', () => { jest.spyOn(contextSrv, 'accessControlEnabled').mockReturnValue(true); describe('Grafana rules', () => { it('Should allow editing when the user has the alert rule update permission and folder permissions', () => { diff --git a/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts b/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts index 5d42819b433..84ba418c685 100644 --- a/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts +++ b/public/app/features/alerting/unified/hooks/useIsRuleEditable.ts @@ -1,7 +1,9 @@ import { contextSrv } from 'app/core/services/context_srv'; import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; + import { getRulesPermissions } from '../utils/access-control'; import { isGrafanaRulerRule } from '../utils/rules'; + import { useFolder } from './useFolder'; import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; diff --git a/public/app/features/alerting/unified/hooks/useManagedAlertStateHistory.ts b/public/app/features/alerting/unified/hooks/useManagedAlertStateHistory.ts index 8fcb6a2201e..115c623a881 100644 --- a/public/app/features/alerting/unified/hooks/useManagedAlertStateHistory.ts +++ b/public/app/features/alerting/unified/hooks/useManagedAlertStateHistory.ts @@ -1,8 +1,11 @@ -import { StateHistoryItem } from 'app/types/unified-alerting'; import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; + +import { StateHistoryItem } from 'app/types/unified-alerting'; + import { fetchGrafanaAnnotationsAction } from '../state/actions'; import { AsyncRequestState } from '../utils/redux'; + import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; export function useManagedAlertStateHistory(alertId: string) { diff --git a/public/app/features/alerting/unified/hooks/useMuteTimingOptions.ts b/public/app/features/alerting/unified/hooks/useMuteTimingOptions.ts index 650ee65ef72..843a7ec3197 100644 --- a/public/app/features/alerting/unified/hooks/useMuteTimingOptions.ts +++ b/public/app/features/alerting/unified/hooks/useMuteTimingOptions.ts @@ -1,8 +1,11 @@ +import { useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { AlertmanagerConfig } from 'app/plugins/datasource/alertmanager/types'; -import { useMemo } from 'react'; + import { timeIntervalToString } from '../utils/alertmanager'; import { initialAsyncRequestState } from '../utils/redux'; + import { useAlertManagerSourceName } from './useAlertManagerSourceName'; import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; diff --git a/public/app/features/alerting/unified/hooks/usePanelCombinedRules.ts b/public/app/features/alerting/unified/hooks/usePanelCombinedRules.ts index 7bdb4b30a72..f7d562bd647 100644 --- a/public/app/features/alerting/unified/hooks/usePanelCombinedRules.ts +++ b/public/app/features/alerting/unified/hooks/usePanelCombinedRules.ts @@ -1,14 +1,18 @@ import { SerializedError } from '@reduxjs/toolkit'; +import { useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; + import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { CombinedRule } from 'app/types/unified-alerting'; -import { useDispatch } from 'react-redux'; + import { fetchPromRulesAction, fetchRulerRulesAction } from '../state/actions'; import { Annotation, RULE_LIST_POLL_INTERVAL_MS } from '../utils/constants'; import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; import { initialAsyncRequestState } from '../utils/redux'; + import { useCombinedRuleNamespaces } from './useCombinedRuleNamespaces'; import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; -import { useEffect, useMemo } from 'react'; + interface Options { dashboard: DashboardModel; panel: PanelModel; diff --git a/public/app/features/alerting/unified/hooks/useRuleSourcesWithRuler.ts b/public/app/features/alerting/unified/hooks/useRuleSourcesWithRuler.ts index e5d49d680ac..da9baf2d197 100644 --- a/public/app/features/alerting/unified/hooks/useRuleSourcesWithRuler.ts +++ b/public/app/features/alerting/unified/hooks/useRuleSourcesWithRuler.ts @@ -1,6 +1,8 @@ import { DataSourceInstanceSettings } from '@grafana/data'; import { PromBasedDataSource } from 'app/types/unified-alerting'; + import { getDataSourceByName } from '../utils/datasource'; + import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector'; export function useRulesSourcesWithRuler(): DataSourceInstanceSettings[] { diff --git a/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx b/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx index 980977e60e5..1e0a5a425f9 100644 --- a/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx +++ b/public/app/features/alerting/unified/hooks/useStateHistoryModal.tsx @@ -1,5 +1,7 @@ import React, { useMemo, useState } from 'react'; + import { Modal } from '@grafana/ui'; + import { StateHistory } from '../components/rules/StateHistory'; function useStateHistoryModal(alertId: string) { diff --git a/public/app/features/alerting/unified/hooks/useURLSearchParams.ts b/public/app/features/alerting/unified/hooks/useURLSearchParams.ts index 9ede7b89937..f67cecda4bb 100644 --- a/public/app/features/alerting/unified/hooks/useURLSearchParams.ts +++ b/public/app/features/alerting/unified/hooks/useURLSearchParams.ts @@ -1,5 +1,6 @@ import { useCallback, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; + import { locationService } from '@grafana/runtime'; export function useURLSearchParams(): [ diff --git a/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts b/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts index 8593feebf97..57b335ebb47 100644 --- a/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts +++ b/public/app/features/alerting/unified/hooks/useUnifiedAlertingSelector.ts @@ -1,5 +1,7 @@ -import { StoreState } from 'app/types'; import { useSelector } from 'react-redux'; + +import { StoreState } from 'app/types'; + import { UnifiedAlertingState } from '../state/reducers'; export function useUnifiedAlertingSelector( diff --git a/public/app/features/alerting/unified/hooks/useVizHeight.ts b/public/app/features/alerting/unified/hooks/useVizHeight.ts index bd3b525e735..74a6f510011 100644 --- a/public/app/features/alerting/unified/hooks/useVizHeight.ts +++ b/public/app/features/alerting/unified/hooks/useVizHeight.ts @@ -1,5 +1,6 @@ import { PanelData } from '@grafana/data'; import { useTheme2 } from '@grafana/ui'; + import { STAT, TIMESERIES } from '../utils/constants'; export function useVizHeight(data: PanelData, pluginId: string, frameIndex: number) { diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 952037e6f44..0dcb67cca28 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -6,19 +6,8 @@ import { DataSourceRef, ScopedVars, } from '@grafana/data'; -import { - GrafanaAlertStateDecision, - GrafanaRuleDefinition, - PromAlertingRuleState, - PromRuleType, - RulerAlertingRuleDTO, - RulerGrafanaRuleDTO, - RulerRuleGroupDTO, - RulerRulesConfigDTO, -} from 'app/types/unified-alerting-dto'; -import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace, CombinedRule } from 'app/types/unified-alerting'; -import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DataSourceSrv, GetDataSourceListFilters, config } from '@grafana/runtime'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AlertmanagerAlert, AlertManagerCortexConfig, @@ -30,6 +19,17 @@ import { SilenceState, } from 'app/plugins/datasource/alertmanager/types'; import { FolderDTO } from 'app/types'; +import { AlertingRule, Alert, RecordingRule, RuleGroup, RuleNamespace, CombinedRule } from 'app/types/unified-alerting'; +import { + GrafanaAlertStateDecision, + GrafanaRuleDefinition, + PromAlertingRuleState, + PromRuleType, + RulerAlertingRuleDTO, + RulerGrafanaRuleDTO, + RulerRuleGroupDTO, + RulerRulesConfigDTO, +} from 'app/types/unified-alerting-dto'; let nextDataSourceId = 1; diff --git a/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts b/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts index ce5c6580251..8e716bd4153 100644 --- a/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts +++ b/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts @@ -1,3 +1,7 @@ +import { Observable, of, throwError } from 'rxjs'; +import { delay, take } from 'rxjs/operators'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; + import { ArrayVector, DataFrame, @@ -12,9 +16,7 @@ import { import { DataSourceSrv, FetchResponse } from '@grafana/runtime'; import { BackendSrv } from 'app/core/services/backend_srv'; import { AlertQuery } from 'app/types/unified-alerting-dto'; -import { Observable, of, throwError } from 'rxjs'; -import { delay, take } from 'rxjs/operators'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; + import { AlertingQueryResponse, AlertingQueryRunner } from './AlertingQueryRunner'; describe('AlertingQueryRunner', () => { diff --git a/public/app/features/alerting/unified/state/AlertingQueryRunner.ts b/public/app/features/alerting/unified/state/AlertingQueryRunner.ts index 4822898c950..7ba736c15f8 100644 --- a/public/app/features/alerting/unified/state/AlertingQueryRunner.ts +++ b/public/app/features/alerting/unified/state/AlertingQueryRunner.ts @@ -1,6 +1,7 @@ import { Observable, of, OperatorFunction, ReplaySubject, Unsubscribable } from 'rxjs'; import { catchError, map, share } from 'rxjs/operators'; import { v4 as uuidv4 } from 'uuid'; + import { dataFrameFromJSON, DataFrameJSON, @@ -13,12 +14,13 @@ import { } from '@grafana/data'; import { FetchResponse, getDataSourceSrv, toDataQueryError } from '@grafana/runtime'; import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; +import { isExpressionQuery } from 'app/features/expressions/guards'; +import { cancelNetworkRequestsOnUnsubscribe } from 'app/features/query/state/processing/canceler'; +import { setStructureRevision } from 'app/features/query/state/processing/revision'; import { preProcessPanelData } from 'app/features/query/state/runRequest'; import { AlertQuery } from 'app/types/unified-alerting-dto'; + import { getTimeRangeForExpression } from '../utils/timeRange'; -import { isExpressionQuery } from 'app/features/expressions/guards'; -import { setStructureRevision } from 'app/features/query/state/processing/revision'; -import { cancelNetworkRequestsOnUnsubscribe } from 'app/features/query/state/processing/canceler'; export interface AlertingQueryResult { frames: DataFrameJSON[]; diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 92df3168f92..071aefa3377 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -1,5 +1,7 @@ -import { getBackendSrv, locationService } from '@grafana/runtime'; import { createAsyncThunk } from '@reduxjs/toolkit'; +import { isEmpty } from 'lodash'; + +import { getBackendSrv, locationService } from '@grafana/runtime'; import { AlertmanagerAlert, AlertManagerCortexConfig, @@ -24,7 +26,7 @@ import { StateHistoryItem, } from 'app/types/unified-alerting'; import { PromApplication, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; -import { isEmpty } from 'lodash'; + import { addAlertManagers, createOrUpdateSilence, @@ -390,8 +392,14 @@ export const saveRuleFormAction = createAsyncThunk( if (redirectOnSave) { locationService.push(redirectOnSave); } else { + // if the identifier comes up empty (this happens when Grafana managed rule moves to another namespace or group) + const stringifiedIdentifier = ruleId.stringifyIdentifier(identifier); + if (!stringifiedIdentifier) { + locationService.push('/alerting/list'); + return; + } // redirect to edit page - const newLocation = `/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`; + const newLocation = `/alerting/${encodeURIComponent(stringifiedIdentifier)}/edit`; if (locationService.getLocation().pathname !== newLocation) { locationService.replace(newLocation); } diff --git a/public/app/features/alerting/unified/state/reducers.ts b/public/app/features/alerting/unified/state/reducers.ts index 30d2ff1d445..41d81124d22 100644 --- a/public/app/features/alerting/unified/state/reducers.ts +++ b/public/app/features/alerting/unified/state/reducers.ts @@ -1,5 +1,7 @@ import { combineReducers } from 'redux'; + import { createAsyncMapSlice, createAsyncSlice } from '../utils/redux'; + import { createOrUpdateSilenceAction, deleteAlertManagerConfigAction, diff --git a/public/app/features/alerting/unified/styles/notifications.ts b/public/app/features/alerting/unified/styles/notifications.ts index 2d5966eb4fc..162d81196f4 100644 --- a/public/app/features/alerting/unified/styles/notifications.ts +++ b/public/app/features/alerting/unified/styles/notifications.ts @@ -1,5 +1,6 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; import { AlertState } from 'app/plugins/datasource/alertmanager/types'; export const getNotificationsTextColors = (theme: GrafanaTheme2) => ({ diff --git a/public/app/features/alerting/unified/styles/table.ts b/public/app/features/alerting/unified/styles/table.ts index 8994219683e..422dd4de9a6 100644 --- a/public/app/features/alerting/unified/styles/table.ts +++ b/public/app/features/alerting/unified/styles/table.ts @@ -1,6 +1,7 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; + export const getAlertTableStyles = (theme: GrafanaTheme2) => ({ table: css` width: 100%; diff --git a/public/app/features/alerting/unified/types/preview.ts b/public/app/features/alerting/unified/types/preview.ts index e94891a656c..13e6315cb92 100644 --- a/public/app/features/alerting/unified/types/preview.ts +++ b/public/app/features/alerting/unified/types/preview.ts @@ -1,5 +1,6 @@ import { PanelData } from '@grafana/data'; import { AlertQuery } from 'app/types/unified-alerting-dto'; + import { RuleFormType } from './rule-form'; export type PreviewRuleRequest = GrafanaPreviewRuleRequest | CloudPreviewRuleRequest; diff --git a/public/app/features/alerting/unified/types/receiver-form.ts b/public/app/features/alerting/unified/types/receiver-form.ts index 126dd194806..121bf6f1559 100644 --- a/public/app/features/alerting/unified/types/receiver-form.ts +++ b/public/app/features/alerting/unified/types/receiver-form.ts @@ -1,6 +1,8 @@ +import React from 'react'; + import { GrafanaManagedReceiverConfig } from 'app/plugins/datasource/alertmanager/types'; import { CloudNotifierType, NotifierType } from 'app/types'; -import React from 'react'; + import { ControlledField } from '../hooks/useControlledFieldArray'; export interface ChannelValues { diff --git a/public/app/features/alerting/unified/types/rule-form.ts b/public/app/features/alerting/unified/types/rule-form.ts index 0ae36bb00b0..068dd2d3e2c 100644 --- a/public/app/features/alerting/unified/types/rule-form.ts +++ b/public/app/features/alerting/unified/types/rule-form.ts @@ -11,6 +11,7 @@ export interface RuleFormValues { name: string; type?: RuleFormType; dataSourceName: string | null; + group: string; labels: Array<{ key: string; value: string }>; annotations: Array<{ key: string; value: string }>; @@ -26,7 +27,6 @@ export interface RuleFormValues { // cortex / loki rules namespace: string; - group: string; forTime: number; forTimeUnit: string; expression: string; diff --git a/public/app/features/alerting/unified/utils/access-control.ts b/public/app/features/alerting/unified/utils/access-control.ts index fa884513679..0f03e12af10 100644 --- a/public/app/features/alerting/unified/utils/access-control.ts +++ b/public/app/features/alerting/unified/utils/access-control.ts @@ -1,6 +1,7 @@ -import { AccessControlAction } from 'app/types'; -import { isGrafanaRulesSource } from './datasource'; import { contextSrv } from 'app/core/services/context_srv'; +import { AccessControlAction } from 'app/types'; + +import { isGrafanaRulesSource } from './datasource'; type RulesSourceType = 'grafana' | 'external'; @@ -8,93 +9,93 @@ function getRulesSourceType(alertManagerSourceName: string): RulesSourceType { return isGrafanaRulesSource(alertManagerSourceName) ? 'grafana' : 'external'; } +const instancesPermissions = { + read: { + grafana: AccessControlAction.AlertingInstanceRead, + external: AccessControlAction.AlertingInstancesExternalRead, + }, + create: { + grafana: AccessControlAction.AlertingInstanceCreate, + external: AccessControlAction.AlertingInstancesExternalWrite, + }, + update: { + grafana: AccessControlAction.AlertingInstanceUpdate, + external: AccessControlAction.AlertingInstancesExternalWrite, + }, + delete: { + grafana: AccessControlAction.AlertingInstanceUpdate, + external: AccessControlAction.AlertingInstancesExternalWrite, + }, +}; + +const notificationsPermissions = { + read: { + grafana: AccessControlAction.AlertingNotificationsRead, + external: AccessControlAction.AlertingNotificationsExternalRead, + }, + create: { + grafana: AccessControlAction.AlertingNotificationsCreate, + external: AccessControlAction.AlertingNotificationsExternalWrite, + }, + update: { + grafana: AccessControlAction.AlertingNotificationsUpdate, + external: AccessControlAction.AlertingNotificationsExternalWrite, + }, + delete: { + grafana: AccessControlAction.AlertingNotificationsDelete, + external: AccessControlAction.AlertingNotificationsExternalWrite, + }, +}; + +const rulesPermissions = { + read: { + grafana: AccessControlAction.AlertingRuleRead, + external: AccessControlAction.AlertingRuleExternalRead, + }, + create: { + grafana: AccessControlAction.AlertingRuleCreate, + external: AccessControlAction.AlertingRuleExternalWrite, + }, + update: { + grafana: AccessControlAction.AlertingRuleUpdate, + external: AccessControlAction.AlertingRuleExternalWrite, + }, + delete: { + grafana: AccessControlAction.AlertingRuleDelete, + external: AccessControlAction.AlertingRuleExternalWrite, + }, +}; + export function getInstancesPermissions(rulesSourceName: string) { const sourceType = getRulesSourceType(rulesSourceName); - const permissions = { - read: { - grafana: AccessControlAction.AlertingInstanceRead, - external: AccessControlAction.AlertingInstancesExternalRead, - }, - create: { - grafana: AccessControlAction.AlertingInstanceCreate, - external: AccessControlAction.AlertingInstancesExternalWrite, - }, - update: { - grafana: AccessControlAction.AlertingInstanceUpdate, - external: AccessControlAction.AlertingInstancesExternalWrite, - }, - delete: { - grafana: AccessControlAction.AlertingInstanceUpdate, - external: AccessControlAction.AlertingInstancesExternalWrite, - }, - }; - return { - read: permissions.read[sourceType], - create: permissions.create[sourceType], - update: permissions.update[sourceType], - delete: permissions.delete[sourceType], + read: instancesPermissions.read[sourceType], + create: instancesPermissions.create[sourceType], + update: instancesPermissions.update[sourceType], + delete: instancesPermissions.delete[sourceType], }; } export function getNotificationsPermissions(rulesSourceName: string) { const sourceType = getRulesSourceType(rulesSourceName); - const permissions = { - read: { - grafana: AccessControlAction.AlertingNotificationsRead, - external: AccessControlAction.AlertingNotificationsExternalRead, - }, - create: { - grafana: AccessControlAction.AlertingNotificationsCreate, - external: AccessControlAction.AlertingNotificationsExternalWrite, - }, - update: { - grafana: AccessControlAction.AlertingNotificationsUpdate, - external: AccessControlAction.AlertingNotificationsExternalWrite, - }, - delete: { - grafana: AccessControlAction.AlertingNotificationsDelete, - external: AccessControlAction.AlertingNotificationsExternalWrite, - }, - }; - return { - read: permissions.read[sourceType], - create: permissions.create[sourceType], - update: permissions.update[sourceType], - delete: permissions.delete[sourceType], + read: notificationsPermissions.read[sourceType], + create: notificationsPermissions.create[sourceType], + update: notificationsPermissions.update[sourceType], + delete: notificationsPermissions.delete[sourceType], }; } export function getRulesPermissions(rulesSourceName: string) { const sourceType = getRulesSourceType(rulesSourceName); - const permissions = { - read: { - grafana: AccessControlAction.AlertingRuleRead, - external: AccessControlAction.AlertingRuleExternalRead, - }, - create: { - grafana: AccessControlAction.AlertingRuleCreate, - external: AccessControlAction.AlertingRuleExternalWrite, - }, - update: { - grafana: AccessControlAction.AlertingRuleUpdate, - external: AccessControlAction.AlertingRuleExternalWrite, - }, - delete: { - grafana: AccessControlAction.AlertingRuleDelete, - external: AccessControlAction.AlertingRuleExternalWrite, - }, - }; - return { - read: permissions.read[sourceType], - create: permissions.create[sourceType], - update: permissions.update[sourceType], - delete: permissions.delete[sourceType], + read: rulesPermissions.read[sourceType], + create: rulesPermissions.create[sourceType], + update: rulesPermissions.update[sourceType], + delete: rulesPermissions.delete[sourceType], }; } @@ -103,3 +104,14 @@ export function evaluateAccess(actions: AccessControlAction[], fallBackUserRoles return contextSrv.evaluatePermission(() => fallBackUserRoles, actions); }; } + +export function getRulesAccess() { + return { + canCreateGrafanaRules: + contextSrv.hasEditPermissionInFolders && + contextSrv.hasAccess(rulesPermissions.create.grafana, contextSrv.isEditor), + canCreateCloudRules: contextSrv.hasAccess(rulesPermissions.create.external, contextSrv.isEditor), + canEditRules: (rulesSourceName: string) => + contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, contextSrv.isEditor), + }; +} diff --git a/public/app/features/alerting/unified/utils/accessControlHooks.ts b/public/app/features/alerting/unified/utils/accessControlHooks.ts new file mode 100644 index 00000000000..59a9b666bc6 --- /dev/null +++ b/public/app/features/alerting/unified/utils/accessControlHooks.ts @@ -0,0 +1,7 @@ +import { useMemo } from 'react'; + +import { getRulesAccess } from './access-control'; + +export function useRulesAccess() { + return useMemo(() => getRulesAccess(), []); +} diff --git a/public/app/features/alerting/unified/utils/alertmanager.test.ts b/public/app/features/alerting/unified/utils/alertmanager.test.ts index b45212275a7..bcef091a5ac 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.test.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.test.ts @@ -1,5 +1,6 @@ import { Matcher, MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types'; import { Labels } from 'app/types/unified-alerting-dto'; + import { parseMatcher, parseMatchers, diff --git a/public/app/features/alerting/unified/utils/alertmanager.ts b/public/app/features/alerting/unified/utils/alertmanager.ts index cf904d2dc54..d569c32442a 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.ts @@ -1,3 +1,5 @@ +import { SelectableValue } from '@grafana/data'; +import { FetchError } from '@grafana/runtime'; import { AlertManagerCortexConfig, MatcherOperator, @@ -7,11 +9,11 @@ import { TimeRange, } from 'app/plugins/datasource/alertmanager/types'; import { Labels } from 'app/types/unified-alerting-dto'; + import { MatcherFieldValue } from '../types/silence-form'; -import { SelectableValue } from '@grafana/data'; + import { getAllDataSources } from './config'; import { DataSourceType } from './datasource'; -import { FetchError } from '@grafana/runtime'; export function addDefaultsToAlertmanagerConfig(config: AlertManagerCortexConfig): AlertManagerCortexConfig { // add default receiver if it does not exist diff --git a/public/app/features/alerting/unified/utils/amroutes.ts b/public/app/features/alerting/unified/utils/amroutes.ts index 8609d24c906..39f0a31089a 100644 --- a/public/app/features/alerting/unified/utils/amroutes.ts +++ b/public/app/features/alerting/unified/utils/amroutes.ts @@ -1,12 +1,15 @@ -import { SelectableValue } from '@grafana/data'; -import { Validate } from 'react-hook-form'; -import { MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types'; -import { FormAmRoute } from '../types/amroutes'; -import { parseInterval, timeOptions } from './time'; import { isUndefined, omitBy } from 'lodash'; +import { Validate } from 'react-hook-form'; + +import { SelectableValue } from '@grafana/data'; +import { MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types'; + +import { FormAmRoute } from '../types/amroutes'; import { MatcherFieldValue } from '../types/silence-form'; + import { matcherToMatcherField, parseMatcher } from './alertmanager'; import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; +import { parseInterval, timeOptions } from './time'; const defaultValueAndType: [string, string] = ['', timeOptions[0].value]; diff --git a/public/app/features/alerting/unified/utils/datasource.ts b/public/app/features/alerting/unified/utils/datasource.ts index e494c62dda3..4a17af4c216 100644 --- a/public/app/features/alerting/unified/utils/datasource.ts +++ b/public/app/features/alerting/unified/utils/datasource.ts @@ -1,6 +1,9 @@ import { DataSourceJsonData, DataSourceInstanceSettings } from '@grafana/data'; +import { contextSrv } from 'app/core/services/context_srv'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; +import { AccessControlAction } from 'app/types'; import { RulesSource } from 'app/types/unified-alerting'; + import { getAllDataSources } from './config'; export const GRAFANA_RULES_SOURCE_NAME = 'grafana'; @@ -15,13 +18,17 @@ export enum DataSourceType { export const RulesDataSourceTypes: string[] = [DataSourceType.Loki, DataSourceType.Prometheus]; export function getRulesDataSources() { + if (!contextSrv.hasPermission(AccessControlAction.AlertingRuleExternalRead)) { + return []; + } + return getAllDataSources() .filter((ds) => RulesDataSourceTypes.includes(ds.type) && ds.jsonData.manageAlerts !== false) .sort((a, b) => a.name.localeCompare(b.name)); } export function getRulesDataSource(rulesSourceName: string) { - return getAllDataSources().find((x) => x.name === rulesSourceName); + return getRulesDataSources().find((x) => x.name === rulesSourceName); } export function getAlertManagerDataSources() { @@ -42,11 +49,23 @@ export function getLotexDataSourceByName(dataSourceName: string): DataSourceInst } export function getAllRulesSourceNames(): string[] { - return [...getRulesDataSources().map((r) => r.name), GRAFANA_RULES_SOURCE_NAME]; + const availableRulesSources: string[] = getRulesDataSources().map((r) => r.name); + + if (contextSrv.hasPermission(AccessControlAction.AlertingRuleRead)) { + availableRulesSources.push(GRAFANA_RULES_SOURCE_NAME); + } + + return availableRulesSources; } export function getAllRulesSources(): RulesSource[] { - return [...getRulesDataSources(), GRAFANA_RULES_SOURCE_NAME]; + const availableRulesSources: RulesSource[] = getRulesDataSources(); + + if (contextSrv.hasPermission(AccessControlAction.AlertingRuleRead)) { + availableRulesSources.push(GRAFANA_RULES_SOURCE_NAME); + } + + return availableRulesSources; } export function getRulesSourceName(rulesSource: RulesSource): string { diff --git a/public/app/features/alerting/unified/utils/matchers.test.ts b/public/app/features/alerting/unified/utils/matchers.test.ts index cedddfe5e55..0a073dde7c4 100644 --- a/public/app/features/alerting/unified/utils/matchers.test.ts +++ b/public/app/features/alerting/unified/utils/matchers.test.ts @@ -1,7 +1,9 @@ import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; -import { getMatcherQueryParams, findAlertInstancesWithMatchers, parseQueryParamMatchers } from './matchers'; + import { mockPromAlert } from '../mocks'; +import { getMatcherQueryParams, findAlertInstancesWithMatchers, parseQueryParamMatchers } from './matchers'; + describe('Unified Alerting matchers', () => { describe('getMatcherQueryParams tests', () => { it('Should create an entry for each label', () => { diff --git a/public/app/features/alerting/unified/utils/matchers.ts b/public/app/features/alerting/unified/utils/matchers.ts index 619a370b40d..66fe014b2bc 100644 --- a/public/app/features/alerting/unified/utils/matchers.ts +++ b/public/app/features/alerting/unified/utils/matchers.ts @@ -1,10 +1,13 @@ -import { Matcher, MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; -import { Labels } from '@grafana/data'; -import { parseMatcher } from './alertmanager'; import { uniqBy } from 'lodash'; -import { MatcherFieldValue } from '../types/silence-form'; + +import { Labels } from '@grafana/data'; +import { Matcher, MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; import { Alert } from 'app/types/unified-alerting'; +import { MatcherFieldValue } from '../types/silence-form'; + +import { parseMatcher } from './alertmanager'; + // Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[] export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] { const parsedMatchers = matcherPairs.filter((x) => !!x.trim()).map((x) => parseMatcher(x.trim())); diff --git a/public/app/features/alerting/unified/utils/misc.test.ts b/public/app/features/alerting/unified/utils/misc.test.ts index c69888b374f..e07d8699771 100644 --- a/public/app/features/alerting/unified/utils/misc.test.ts +++ b/public/app/features/alerting/unified/utils/misc.test.ts @@ -1,7 +1,7 @@ import { sortAlerts } from 'app/features/alerting/unified/utils/misc'; -import { GrafanaAlertState } from 'app/types/unified-alerting-dto'; -import { Alert } from 'app/types/unified-alerting'; import { SortOrder } from 'app/plugins/panel/alertlist/types'; +import { Alert } from 'app/types/unified-alerting'; +import { GrafanaAlertState } from 'app/types/unified-alerting-dto'; function withState(state: GrafanaAlertState, labels?: {}): Alert { return { activeAt: '', annotations: {}, labels: labels || {}, state: state, value: '' }; diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 0b61bef8ad5..71ada51cded 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -1,14 +1,16 @@ +import { sortBy } from 'lodash'; + import { urlUtil, UrlQueryMap, Labels } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules'; +import { SortOrder } from 'app/plugins/panel/alertlist/types'; import { Alert, CombinedRule, FilterState, RulesSource, SilenceFilterState } from 'app/types/unified-alerting'; +import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; + import { ALERTMANAGER_NAME_QUERY_KEY } from './constants'; import { getRulesSourceName } from './datasource'; -import * as ruleId from './rule-id'; -import { SortOrder } from 'app/plugins/panel/alertlist/types'; -import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules'; -import { sortBy } from 'lodash'; -import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { getMatcherQueryParams } from './matchers'; +import * as ruleId from './rule-id'; export function createViewLink(ruleSource: RulesSource, rule: CombinedRule, returnTo: string): string { const sourceName = getRulesSourceName(ruleSource); diff --git a/public/app/features/alerting/unified/utils/mute-timings.ts b/public/app/features/alerting/unified/utils/mute-timings.ts index c5afe46d326..47e51df4935 100644 --- a/public/app/features/alerting/unified/utils/mute-timings.ts +++ b/public/app/features/alerting/unified/utils/mute-timings.ts @@ -1,5 +1,7 @@ -import { MuteTimeInterval, TimeInterval } from 'app/plugins/datasource/alertmanager/types'; import { omitBy, isUndefined } from 'lodash'; + +import { MuteTimeInterval, TimeInterval } from 'app/plugins/datasource/alertmanager/types'; + import { MuteTimingFields, MuteTimingIntervalFields } from '../types/mute-timing-form'; export const DAYS_OF_THE_WEEK = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; diff --git a/public/app/features/alerting/unified/utils/query.test.ts b/public/app/features/alerting/unified/utils/query.test.ts index 185efb26870..5a593a088de 100644 --- a/public/app/features/alerting/unified/utils/query.test.ts +++ b/public/app/features/alerting/unified/utils/query.test.ts @@ -1,9 +1,10 @@ import { DataSourceJsonData, PluginMeta } from '@grafana/data'; -import { alertRuleToQueries } from './query'; -import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; +import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; import { CombinedRule } from 'app/types/unified-alerting'; import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; -import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; + +import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; +import { alertRuleToQueries } from './query'; describe('alertRuleToQueries', () => { it('it should convert grafana alert', () => { diff --git a/public/app/features/alerting/unified/utils/query.ts b/public/app/features/alerting/unified/utils/query.ts index 835c5f33f24..7fd17d02703 100644 --- a/public/app/features/alerting/unified/utils/query.ts +++ b/public/app/features/alerting/unified/utils/query.ts @@ -3,6 +3,7 @@ import { LokiQuery } from 'app/plugins/datasource/loki/types'; import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { CombinedRule } from 'app/types/unified-alerting'; import { AlertQuery } from 'app/types/unified-alerting-dto'; + import { isCloudRulesSource, isGrafanaRulesSource } from './datasource'; import { isGrafanaRulerRule } from './rules'; diff --git a/public/app/features/alerting/unified/utils/receiver-form.ts b/public/app/features/alerting/unified/utils/receiver-form.ts index f4aa3886fa6..7c0c33dc101 100644 --- a/public/app/features/alerting/unified/utils/receiver-form.ts +++ b/public/app/features/alerting/unified/utils/receiver-form.ts @@ -1,3 +1,5 @@ +import { isArray } from 'lodash'; + import { AlertManagerCortexConfig, GrafanaManagedReceiverConfig, @@ -5,7 +7,7 @@ import { Route, } from 'app/plugins/datasource/alertmanager/types'; import { CloudNotifierType, NotifierDTO, NotifierType } from 'app/types'; -import { isArray } from 'lodash'; + import { CloudChannelConfig, CloudChannelMap, diff --git a/public/app/features/alerting/unified/utils/receivers.ts b/public/app/features/alerting/unified/utils/receivers.ts index fbde0d532ea..c599e45514b 100644 --- a/public/app/features/alerting/unified/utils/receivers.ts +++ b/public/app/features/alerting/unified/utils/receivers.ts @@ -1,7 +1,8 @@ +import { capitalize } from 'lodash'; + import { receiverTypeNames } from 'app/plugins/datasource/alertmanager/consts'; import { GrafanaManagedReceiverConfig, Receiver } from 'app/plugins/datasource/alertmanager/types'; import { NotifierDTO } from 'app/types'; -import { capitalize } from 'lodash'; // extract notifier type name to count map, eg { Slack: 1, Email: 2 } diff --git a/public/app/features/alerting/unified/utils/redux.ts b/public/app/features/alerting/unified/utils/redux.ts index 9320077dab7..d1eba541758 100644 --- a/public/app/features/alerting/unified/utils/redux.ts +++ b/public/app/features/alerting/unified/utils/redux.ts @@ -1,8 +1,9 @@ import { AsyncThunk, createSlice, Draft, isAsyncThunkAction, PayloadAction, SerializedError } from '@reduxjs/toolkit'; -import { FetchError } from '@grafana/runtime'; -import { AppEvents } from '@grafana/data'; +import { AppEvents } from '@grafana/data'; +import { FetchError } from '@grafana/runtime'; import { appEvents } from 'app/core/core'; + import { isFetchError } from './alertmanager'; export interface AsyncRequestState { diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index bf9326f252e..e2fe65c394c 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -1,39 +1,43 @@ import { DataQuery, + DataSourceRef, + getDefaultRelativeTimeRange, + IntervalValues, rangeUtil, RelativeTimeRange, ScopedVars, - getDefaultRelativeTimeRange, TimeRange, - IntervalValues, - DataSourceRef, } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; -import { contextSrv } from 'app/core/services/context_srv'; +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { getNextRefIdChar } from 'app/core/utils/query'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource'; import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; import { RuleWithLocation } from 'app/types/unified-alerting'; import { + AlertQuery, Annotations, GrafanaAlertStateDecision, - AlertQuery, Labels, PostableRuleGrafanaRuleDTO, RulerRuleDTO, } from 'app/types/unified-alerting-dto'; + import { EvalFunction } from '../../state/alertDef'; import { RuleFormType, RuleFormValues } from '../types/rule-form'; + +import { getRulesAccess } from './access-control'; import { Annotation } from './constants'; import { isGrafanaRulesSource } from './datasource'; import { arrayToRecord, recordToArray } from './misc'; import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './rules'; import { parseInterval } from './time'; -import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; -export const getDefaultFormValues = (): RuleFormValues => - Object.freeze({ +export const getDefaultFormValues = (): RuleFormValues => { + const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess(); + + return Object.freeze({ name: '', labels: [{ key: '', value: '' }], annotations: [ @@ -42,7 +46,8 @@ export const getDefaultFormValues = (): RuleFormValues => { key: Annotation.runbookURL, value: '' }, ], dataSourceName: null, - type: !contextSrv.isEditor ? RuleFormType.grafana : undefined, // viewers can't create prom alerts + type: canCreateGrafanaRules ? RuleFormType.grafana : canCreateCloudRules ? RuleFormType.cloudAlerting : undefined, // viewers can't create prom alerts + group: '', // grafana folder: null, @@ -54,12 +59,12 @@ export const getDefaultFormValues = (): RuleFormValues => evaluateFor: '5m', // cortex / loki - group: '', namespace: '', expression: '', forTime: 1, forTimeUnit: 'm', }); +}; export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO { const { name, expression, forTime, forTimeUnit, type } = values; @@ -115,6 +120,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF ...defaultFormValues, name: ga.title, type: RuleFormType.grafana, + group: group.name, evaluateFor: rule.for || '0', evaluateEvery: group.interval || defaultFormValues.evaluateEvery, noDataState: ga.no_data_state, diff --git a/public/app/features/alerting/unified/utils/rule-id.ts b/public/app/features/alerting/unified/utils/rule-id.ts index 08454d58f12..1b580cb82d6 100644 --- a/public/app/features/alerting/unified/utils/rule-id.ts +++ b/public/app/features/alerting/unified/utils/rule-id.ts @@ -1,5 +1,6 @@ import { CombinedRule, Rule, RuleIdentifier, RuleWithLocation } from 'app/types/unified-alerting'; import { Annotations, Labels, RulerRuleDTO } from 'app/types/unified-alerting-dto'; + import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; import { isAlertingRule, diff --git a/public/app/features/alerting/unified/utils/rulerClient.test.ts b/public/app/features/alerting/unified/utils/rulerClient.test.ts deleted file mode 100644 index ec5771833cf..00000000000 --- a/public/app/features/alerting/unified/utils/rulerClient.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { RulerRuleGroupDTO } from 'app/types/unified-alerting-dto'; -import { getUniqueGroupName } from './rulerClient'; - -describe('getUniqueGroupName', () => { - it('Should return the original value when there are no duplicates', () => { - // Arrange - const originalGroupName = 'file-system-out-of-space'; - const existingGroups: RulerRuleGroupDTO[] = []; - - // Act - const groupName = getUniqueGroupName(originalGroupName, existingGroups); - - // Assert - expect(groupName).toBe(originalGroupName); - }); - - it('Should increment suffix counter until a unique name created', () => { - // Arrange - const originalGroupName = 'file-system-out-of-space'; - const existingGroups: RulerRuleGroupDTO[] = [ - { name: 'file-system-out-of-space', rules: [] }, - { name: 'file-system-out-of-space-2', rules: [] }, - ]; - - // Act - const groupName = getUniqueGroupName(originalGroupName, existingGroups); - - // Assert - expect(groupName).toBe('file-system-out-of-space-3'); - }); -}); diff --git a/public/app/features/alerting/unified/utils/rulerClient.ts b/public/app/features/alerting/unified/utils/rulerClient.ts index 89eb8e6c286..f8fd8ffab05 100644 --- a/public/app/features/alerting/unified/utils/rulerClient.ts +++ b/public/app/features/alerting/unified/utils/rulerClient.ts @@ -1,15 +1,16 @@ import { RuleIdentifier, RulerDataSourceConfig, RuleWithLocation } from 'app/types/unified-alerting'; -import { PostableRulerRuleGroupDTO, RulerGrafanaRuleDTO, RulerRuleGroupDTO } from 'app/types/unified-alerting-dto'; import { - deleteRulerRulesGroup, - fetchRulerRulesGroup, - fetchRulerRulesNamespace, - fetchRulerRules, - setRulerRuleGroup, -} from '../api/ruler'; + PostableRuleGrafanaRuleDTO, + PostableRulerRuleGroupDTO, + RulerGrafanaRuleDTO, + RulerRuleGroupDTO, +} from 'app/types/unified-alerting-dto'; + +import { deleteRulerRulesGroup, fetchRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from '../api/ruler'; import { RuleFormValues } from '../types/rule-form'; import * as ruleId from '../utils/rule-id'; -import { GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from './datasource'; + +import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; import { formValuesToRulerGrafanaRuleDTO, formValuesToRulerRuleDTO } from './rule-form'; import { isCloudRuleIdentifier, @@ -25,16 +26,6 @@ export interface RulerClient { saveGrafanaRule(values: RuleFormValues, existing?: RuleWithLocation): Promise; } -export function getUniqueGroupName(currentGroupName: string, existingGroups: RulerRuleGroupDTO[]) { - let newGroupName = currentGroupName; - let idx = 1; - while (!!existingGroups.find((g) => g.name === newGroupName)) { - newGroupName = `${currentGroupName}-${++idx}`; - } - - return newGroupName; -} - export function getRulerClient(rulerConfig: RulerDataSourceConfig): RulerClient { const findEditableRule = async (ruleIdentifier: RuleIdentifier): Promise => { if (isGrafanaRuleIdentifier(ruleIdentifier)) { @@ -90,13 +81,8 @@ export function getRulerClient(rulerConfig: RulerDataSourceConfig): RulerClient }; const deleteRule = async (ruleWithLocation: RuleWithLocation): Promise => { - const { ruleSourceName, namespace, group, rule } = ruleWithLocation; - // in case of GRAFANA, each group implicitly only has one rule. delete the group. - if (isGrafanaRulesSource(ruleSourceName)) { - await deleteRulerRulesGroup(rulerConfig, namespace, group.name); - return; - } - // in case of CLOUD + const { namespace, group, rule } = ruleWithLocation; + // it was the last rule, delete the entire group if (group.rules.length === 1) { await deleteRulerRulesGroup(rulerConfig, namespace, group.name); @@ -157,65 +143,104 @@ export function getRulerClient(rulerConfig: RulerDataSourceConfig): RulerClient } }; - const saveGrafanaRule = async (values: RuleFormValues, existing?: RuleWithLocation): Promise => { - const { folder, evaluateEvery } = values; - const formRule = formValuesToRulerGrafanaRuleDTO(values); - + const saveGrafanaRule = async (values: RuleFormValues, existingRule?: RuleWithLocation): Promise => { + const { folder, group, evaluateEvery } = values; if (!folder) { throw new Error('Folder must be specified'); } - // updating an existing rule... - if (existing) { - // refetch it to be sure we have the latest - const freshExisting = await findEditableRule(ruleId.fromRuleWithLocation(existing)); - if (!freshExisting) { - throw new Error('Rule not found.'); - } + const newRule = formValuesToRulerGrafanaRuleDTO(values); + const namespace = folder.title; + const groupSpec = { name: group, interval: evaluateEvery }; - // if same folder, repost the group with updated rule - if (freshExisting.namespace === folder.title) { - const uid = (freshExisting.rule as RulerGrafanaRuleDTO).grafana_alert.uid!; - formRule.grafana_alert.uid = uid; - await setRulerRuleGroup(rulerConfig, freshExisting.namespace, { - name: freshExisting.group.name, - interval: evaluateEvery, - rules: [formRule], - }); - return { uid, ruleSourceName: 'grafana' }; - } + if (!existingRule) { + return addRuleToNamespaceAndGroup(namespace, groupSpec, newRule); } - // if creating new rule or folder was changed, create rule in a new group - const targetFolderGroups = await fetchRulerRulesNamespace(rulerConfig, folder.title); + const sameNamespace = existingRule.namespace === namespace; + const sameGroup = existingRule.group.name === values.group; + const sameLocation = sameNamespace && sameGroup; - // set group name to rule name, but be super paranoid and check that this group does not already exist - const groupName = getUniqueGroupName(values.name, targetFolderGroups); - formRule.grafana_alert.title = groupName; + if (sameLocation) { + // we're update a rule in the same namespace and group + return updateGrafanaRule(existingRule, newRule, evaluateEvery); + } else { + // we're moving a rule to either a different group or namespace + return moveGrafanaRule(namespace, groupSpec, existingRule, newRule); + } + }; + + const addRuleToNamespaceAndGroup = async ( + namespace: string, + group: { name: string; interval: string }, + newRule: PostableRuleGrafanaRuleDTO + ): Promise => { + const existingGroup = await fetchRulerRulesGroup(rulerConfig, namespace, group.name); + if (!existingGroup) { + throw new Error(`No group found with name "${group.name}"`); + } const payload: PostableRulerRuleGroupDTO = { - name: groupName, - interval: evaluateEvery, - rules: [formRule], + name: group.name, + interval: group.interval, + rules: (existingGroup.rules ?? []).concat(newRule as RulerGrafanaRuleDTO), }; - await setRulerRuleGroup(rulerConfig, folder.title, payload); - // now refetch this group to get the uid, hah - const result = await fetchRulerRulesGroup(rulerConfig, folder.title, groupName); - const newUid = (result?.rules[0] as RulerGrafanaRuleDTO)?.grafana_alert?.uid; - if (newUid) { - // if folder has changed, delete the old one - if (existing) { - const freshExisting = await findEditableRule(ruleId.fromRuleWithLocation(existing)); - if (freshExisting && freshExisting.namespace !== folder.title) { - await deleteRule(freshExisting); - } - } + await setRulerRuleGroup(rulerConfig, namespace, payload); - return { uid: newUid, ruleSourceName: 'grafana' }; - } else { - throw new Error('Failed to fetch created rule.'); + return { uid: '', ruleSourceName: GRAFANA_RULES_SOURCE_NAME }; + }; + + // we can't move the rule in a single atomic operation so we have to + // 1. add the rule to the new group + // 2. remove the rule from the old one + const moveGrafanaRule = async ( + namespace: string, + group: { name: string; interval: string }, + existingRule: RuleWithLocation, + newRule: PostableRuleGrafanaRuleDTO + ): Promise => { + // add the new rule to the requested namespace and group + const identifier = await addRuleToNamespaceAndGroup(namespace, group, newRule); + + // remove the rule from the previous namespace and group + await deleteRule({ + ruleSourceName: existingRule.ruleSourceName, + namespace: existingRule.namespace, + group: existingRule.group, + rule: newRule as RulerGrafanaRuleDTO, + }); + + return identifier; + }; + + const updateGrafanaRule = async ( + existingRule: RuleWithLocation, + newRule: PostableRuleGrafanaRuleDTO, + interval: string + ): Promise => { + // type guard to make sure we're working with a Grafana managed rule + if (!isGrafanaRulerRule(existingRule.rule)) { + throw new Error('The rule is not a Grafana managed rule'); } + + // make sure our updated alert has the same UID as before + const uid = existingRule.rule.grafana_alert.uid; + newRule.grafana_alert.uid = uid; + + // create the new array of rules we want to send to the group + const newRules = existingRule.group.rules + .filter((rule): rule is RulerGrafanaRuleDTO => isGrafanaRulerRule(rule)) + .filter((rule) => rule.grafana_alert.uid !== uid) + .concat(newRule as RulerGrafanaRuleDTO); + + await setRulerRuleGroup(rulerConfig, existingRule.namespace, { + name: existingRule.group.name, + interval: interval, + rules: newRules, + }); + + return { uid: '', ruleSourceName: GRAFANA_RULES_SOURCE_NAME }; }; // Would be nice to somehow align checking of ruler type between different methods diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 1e4b31a31ed..d9457c61831 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -1,13 +1,6 @@ +import { capitalize } from 'lodash'; + import { AlertState } from '@grafana/data'; -import { - GrafanaAlertState, - PromAlertingRuleState, - PromRuleType, - RulerAlertingRuleDTO, - RulerGrafanaRuleDTO, - RulerRecordingRuleDTO, - RulerRuleDTO, -} from 'app/types/unified-alerting-dto'; import { Alert, AlertingRule, @@ -21,11 +14,21 @@ import { RuleIdentifier, RuleNamespace, } from 'app/types/unified-alerting'; -import { AsyncRequestState } from './redux'; -import { RULER_NOT_SUPPORTED_MSG } from './constants'; -import { capitalize } from 'lodash'; +import { + GrafanaAlertState, + PromAlertingRuleState, + PromRuleType, + RulerAlertingRuleDTO, + RulerGrafanaRuleDTO, + RulerRecordingRuleDTO, + RulerRuleDTO, +} from 'app/types/unified-alerting-dto'; + import { State } from '../components/StateTag'; +import { RULER_NOT_SUPPORTED_MSG } from './constants'; +import { AsyncRequestState } from './redux'; + export function isAlertingRule(rule: Rule | undefined): rule is AlertingRule { return typeof rule === 'object' && rule.type === PromRuleType.Alerting; } diff --git a/public/app/features/alerting/unified/utils/time.ts b/public/app/features/alerting/unified/utils/time.ts index 7d2919bcc27..d6a18f9c634 100644 --- a/public/app/features/alerting/unified/utils/time.ts +++ b/public/app/features/alerting/unified/utils/time.ts @@ -1,4 +1,5 @@ import { describeInterval } from '@grafana/data/src/datetime/rangeutil'; + import { TimeOptions } from '../types/time'; export function parseInterval(value: string): [number, string] { diff --git a/public/app/features/alerting/unified/utils/timeRange.test.ts b/public/app/features/alerting/unified/utils/timeRange.test.ts index b3d80a3b2a0..c10535abe45 100644 --- a/public/app/features/alerting/unified/utils/timeRange.test.ts +++ b/public/app/features/alerting/unified/utils/timeRange.test.ts @@ -1,9 +1,10 @@ import { ReducerID } from '@grafana/data'; -import { getTimeRangeForExpression } from './timeRange'; -import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; +import { defaultCondition } from 'app/features/expressions/utils/expressionTypes'; import { AlertQuery } from 'app/types/unified-alerting-dto'; +import { getTimeRangeForExpression } from './timeRange'; + describe('timeRange', () => { describe('getTimeRangeForExpression', () => { describe('classic condition', () => { diff --git a/public/app/features/alerting/unified/utils/timeRange.ts b/public/app/features/alerting/unified/utils/timeRange.ts index 1e335b4ebcd..10091b2c987 100644 --- a/public/app/features/alerting/unified/utils/timeRange.ts +++ b/public/app/features/alerting/unified/utils/timeRange.ts @@ -1,5 +1,6 @@ import { RelativeTimeRange } from '@grafana/data'; import { AlertQuery } from 'app/types/unified-alerting-dto'; + import { ExpressionQuery, ExpressionQueryType } from '../../../expressions/types'; const FALL_BACK_TIME_RANGE = { from: 21600, to: 0 }; diff --git a/public/app/features/alerting/utils/notificationChannel.test.ts b/public/app/features/alerting/utils/notificationChannel.test.ts index 1ef704217e0..757c9f3477f 100644 --- a/public/app/features/alerting/utils/notificationChannel.test.ts +++ b/public/app/features/alerting/utils/notificationChannel.test.ts @@ -1,6 +1,7 @@ -import { transformSubmitData } from './notificationChannels'; import { NotificationChannelDTO } from '../../../types'; +import { transformSubmitData } from './notificationChannels'; + const basicFormData: NotificationChannelDTO = { id: 1, uid: 'pX7fbbHGk', diff --git a/public/app/features/alerting/utils/notificationChannels.ts b/public/app/features/alerting/utils/notificationChannels.ts index 1aa3e36c697..7c9419ba39f 100644 --- a/public/app/features/alerting/utils/notificationChannels.ts +++ b/public/app/features/alerting/utils/notificationChannels.ts @@ -1,4 +1,5 @@ import memoizeOne from 'memoize-one'; + import { SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; import { NotificationChannelDTO, NotificationChannelType } from 'app/types'; diff --git a/public/app/features/annotations/api.ts b/public/app/features/annotations/api.ts index cdbaab32faf..ebb8e683650 100644 --- a/public/app/features/annotations/api.ts +++ b/public/app/features/annotations/api.ts @@ -1,5 +1,6 @@ import { AnnotationEvent } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; + import { AnnotationTagsResponse } from './types'; export function saveAnnotation(annotation: AnnotationEvent) { diff --git a/public/app/features/annotations/components/AnnotationResultMapper.tsx b/public/app/features/annotations/components/AnnotationResultMapper.tsx index 60fa5ad0f5d..4d7de4c4588 100644 --- a/public/app/features/annotations/components/AnnotationResultMapper.tsx +++ b/public/app/features/annotations/components/AnnotationResultMapper.tsx @@ -10,9 +10,9 @@ import { AnnotationEventFieldSource, getValueFormat, } from '@grafana/data'; +import { Select, Tooltip, Icon } from '@grafana/ui'; import { annotationEventNames, AnnotationFieldInfo } from '../standardAnnotationSupport'; -import { Select, Tooltip, Icon } from '@grafana/ui'; import { AnnotationQueryResponse } from '../types'; // const valueOptions: Array> = [ diff --git a/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx b/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx index f0937dbdac3..3cf223b504c 100644 --- a/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx +++ b/public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx @@ -1,15 +1,17 @@ +import { css, cx } from '@emotion/css'; import React, { PureComponent } from 'react'; import { lastValueFrom } from 'rxjs'; -import { css, cx } from '@emotion/css'; + import { AnnotationEventMappings, AnnotationQuery, DataQuery, DataSourceApi, LoadingState } from '@grafana/data'; import { Button, Icon, IconName, Spinner } from '@grafana/ui'; - import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { standardAnnotationSupport } from '../standardAnnotationSupport'; -import { executeAnnotationQuery } from '../executeAnnotationQuery'; import { PanelModel } from 'app/features/dashboard/state'; + +import { executeAnnotationQuery } from '../executeAnnotationQuery'; +import { standardAnnotationSupport } from '../standardAnnotationSupport'; import { AnnotationQueryResponse } from '../types'; + import { AnnotationFieldMapper } from './AnnotationResultMapper'; interface Props { diff --git a/public/app/features/annotations/executeAnnotationQuery.ts b/public/app/features/annotations/executeAnnotationQuery.ts index 3fe88217ad3..7eab14c8cb5 100644 --- a/public/app/features/annotations/executeAnnotationQuery.ts +++ b/public/app/features/annotations/executeAnnotationQuery.ts @@ -1,11 +1,13 @@ import { Observable, of } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; + import { CoreApp, DataQueryRequest, DataSourceApi, rangeUtil, ScopedVars } from '@grafana/data'; -import { AnnotationQueryOptions, AnnotationQueryResponse } from './types'; -import { standardAnnotationSupport } from './standardAnnotationSupport'; import { runRequest } from '../query/state/runRequest'; +import { standardAnnotationSupport } from './standardAnnotationSupport'; +import { AnnotationQueryOptions, AnnotationQueryResponse } from './types'; + let counter = 100; function getNextRequestId() { return 'AQ' + counter++; diff --git a/public/app/features/annotations/standardAnnotationSupport.test.ts b/public/app/features/annotations/standardAnnotationSupport.test.ts index 9af8c457b4e..ac57a692814 100644 --- a/public/app/features/annotations/standardAnnotationSupport.test.ts +++ b/public/app/features/annotations/standardAnnotationSupport.test.ts @@ -1,4 +1,5 @@ import { FieldType, toDataFrame } from '@grafana/data'; + import { getAnnotationsFromData } from './standardAnnotationSupport'; describe('DataFrame to annotations', () => { diff --git a/public/app/features/annotations/standardAnnotationSupport.ts b/public/app/features/annotations/standardAnnotationSupport.ts index edc6ac668ca..079a506a94b 100644 --- a/public/app/features/annotations/standardAnnotationSupport.ts +++ b/public/app/features/annotations/standardAnnotationSupport.ts @@ -1,5 +1,7 @@ +import { isString } from 'lodash'; import { Observable, of, OperatorFunction } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; + import { AnnotationEvent, AnnotationEventFieldSource, @@ -14,8 +16,6 @@ import { standardTransformers, } from '@grafana/data'; -import { isString } from 'lodash'; - export const standardAnnotationSupport: AnnotationSupport = { /** * Assume the stored value is standard model. diff --git a/public/app/features/annotations/types.ts b/public/app/features/annotations/types.ts index 6722de3d49e..c3ee8e6d359 100644 --- a/public/app/features/annotations/types.ts +++ b/public/app/features/annotations/types.ts @@ -1,4 +1,5 @@ import { AnnotationEvent, PanelData, TimeRange } from '@grafana/data'; + import { DashboardModel, PanelModel } from '../dashboard/state'; export interface AnnotationQueryOptions { diff --git a/public/app/features/api-keys/ApiKeysActionBar.tsx b/public/app/features/api-keys/ApiKeysActionBar.tsx index ef000521a4e..ef182d1c799 100644 --- a/public/app/features/api-keys/ApiKeysActionBar.tsx +++ b/public/app/features/api-keys/ApiKeysActionBar.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { Button, FilterInput } from '@grafana/ui'; interface Props { diff --git a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx index 82a3d7de087..35e0a556e76 100644 --- a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx +++ b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow } from 'enzyme'; +import React from 'react'; + import { ApiKeysAddedModal, Props } from './ApiKeysAddedModal'; const setup = (propOverrides?: object) => { diff --git a/public/app/features/api-keys/ApiKeysAddedModal.tsx b/public/app/features/api-keys/ApiKeysAddedModal.tsx index b345416b588..26bfe2b8e26 100644 --- a/public/app/features/api-keys/ApiKeysAddedModal.tsx +++ b/public/app/features/api-keys/ApiKeysAddedModal.tsx @@ -1,10 +1,12 @@ -import React, { useCallback } from 'react'; -import { css } from '@emotion/css'; -import { Alert, Field, Modal, useStyles2, Input, Icon, ClipboardButton } from '@grafana/ui'; +import { css } from '@emotion/css'; +import React, { useCallback } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { Alert, Field, Modal, useStyles2, Input, Icon, ClipboardButton } from '@grafana/ui'; + import { notifyApp } from '../../core/actions'; -import { dispatch } from '../../store/store'; import { createSuccessNotification } from '../../core/copy/appNotification'; +import { dispatch } from '../../store/store'; export interface Props { onDismiss: () => void; diff --git a/public/app/features/api-keys/ApiKeysForm.tsx b/public/app/features/api-keys/ApiKeysForm.tsx index 9ad93b70f2a..df68660ab41 100644 --- a/public/app/features/api-keys/ApiKeysForm.tsx +++ b/public/app/features/api-keys/ApiKeysForm.tsx @@ -1,10 +1,12 @@ import React, { ChangeEvent, FC, FormEvent, useEffect, useState } from 'react'; -import { EventsWithValidation, LegacyForms, ValidationEvents, Button, Select, InlineField } from '@grafana/ui'; -import { NewApiKey, OrgRole } from '../../types'; + import { rangeUtil, SelectableValue } from '@grafana/data'; -import { SlideDown } from '../../core/components/Animations/SlideDown'; +import { EventsWithValidation, LegacyForms, ValidationEvents, Button, Select, InlineField } from '@grafana/ui'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import { SlideDown } from '../../core/components/Animations/SlideDown'; +import { NewApiKey, OrgRole } from '../../types'; + const { Input } = LegacyForms; const ROLE_OPTIONS: Array> = Object.keys(OrgRole).map((role) => ({ label: role, @@ -15,6 +17,7 @@ interface Props { show: boolean; onClose: () => void; onKeyAdded: (apiKey: NewApiKey) => void; + disabled: boolean; } function isValidInterval(value: string): boolean { @@ -40,7 +43,7 @@ const timeRangeValidationEvents: ValidationEvents = { const tooltipText = 'The API key life duration. For example, 1d if your key is going to last for one day. Supported units are: s,m,h,d,w,M,y'; -export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded }) => { +export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded, disabled }) => { const [name, setName] = useState(''); const [role, setRole] = useState(OrgRole.Viewer); const [secondsToLive, setSecondsToLive] = useState(''); @@ -102,7 +105,7 @@ export const ApiKeysForm: FC = ({ show, onClose, onKeyAdded }) => {
- +
diff --git a/public/app/features/api-keys/ApiKeysPage.test.tsx b/public/app/features/api-keys/ApiKeysPage.test.tsx index e7adf743e62..e4869316130 100644 --- a/public/app/features/api-keys/ApiKeysPage.test.tsx +++ b/public/app/features/api-keys/ApiKeysPage.test.tsx @@ -1,15 +1,18 @@ -import React from 'react'; import { render, screen, within } from '@testing-library/react'; -import { ApiKeysPageUnconnected, Props } from './ApiKeysPage'; -import { ApiKey, OrgRole } from 'app/types'; +import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; +import React from 'react'; + import { NavModel } from '@grafana/data'; -import { setSearchQuery } from './state/reducers'; -import { mockToolkitActionCreator } from '../../../test/core/redux/mocks'; -import { getMultipleMockKeys } from './__mocks__/apiKeysMock'; import { selectors } from '@grafana/e2e-selectors'; -import userEvent from '@testing-library/user-event'; +import { ApiKey, OrgRole } from 'app/types'; + +import { mockToolkitActionCreator } from '../../../test/core/redux/mocks'; import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput'; +import { ApiKeysPageUnconnected, Props } from './ApiKeysPage'; +import { getMultipleMockKeys } from './__mocks__/apiKeysMock'; +import { setSearchQuery } from './state/reducers'; + const setup = (propOverrides: Partial) => { const loadApiKeysMock = jest.fn(); const deleteApiKeyMock = jest.fn(); @@ -37,6 +40,9 @@ const setup = (propOverrides: Partial) => { includeExpired: false, includeExpiredDisabled: false, toggleIncludeExpired: toggleIncludeExpiredMock, + canRead: true, + canCreate: true, + canDelete: true, }; Object.assign(props, propOverrides); @@ -97,7 +103,7 @@ describe('ApiKeysPage', () => { const apiKeys = getMultipleMockKeys(3); const { toggleIncludeExpiredMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); - toggleShowExpired(); + await toggleShowExpired(); expect(toggleIncludeExpiredMock).toHaveBeenCalledTimes(1); }); }); @@ -109,7 +115,7 @@ describe('ApiKeysPage', () => { setSearchQueryMock.mockClear(); expect(screen.getByPlaceholderText(/search keys/i)).toBeInTheDocument(); - userEvent.type(screen.getByPlaceholderText(/search keys/i), 'First'); + await userEvent.type(screen.getByPlaceholderText(/search keys/i), 'First'); expect(setSearchQueryMock).toHaveBeenCalledTimes(5); }); }); @@ -127,21 +133,21 @@ describe('ApiKeysPage', () => { deleteApiKeyMock.mockClear(); expect(within(firstRow).getByLabelText('Delete API key')).toBeInTheDocument(); - userEvent.click(within(firstRow).getByLabelText('Delete API key')); + await userEvent.click(within(firstRow).getByLabelText('Delete API key')); expect(within(firstRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument(); - userEvent.click(within(firstRow).getByRole('button', { name: /delete$/i })); + await userEvent.click(within(firstRow).getByRole('button', { name: /delete$/i })); expect(deleteApiKeyMock).toHaveBeenCalledTimes(1); expect(deleteApiKeyMock).toHaveBeenCalledWith(1); - toggleShowExpired(); + await toggleShowExpired(); deleteApiKeyMock.mockClear(); expect(within(secondRow).getByLabelText('Delete API key')).toBeInTheDocument(); - userEvent.click(within(secondRow).getByLabelText('Delete API key')); + await userEvent.click(within(secondRow).getByLabelText('Delete API key')); expect(within(secondRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument(); - userEvent.click(within(secondRow).getByRole('button', { name: /delete$/i }), undefined, { - skipPointerEventsCheck: true, + await userEvent.click(within(secondRow).getByRole('button', { name: /delete$/i }), { + pointerEventsCheck: PointerEventsCheckLevel.Never, }); expect(deleteApiKeyMock).toHaveBeenCalledTimes(1); expect(deleteApiKeyMock).toHaveBeenCalledWith(2); @@ -154,7 +160,7 @@ describe('ApiKeysPage', () => { const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); addApiKeyMock.mockClear(); - userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key'))); + await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('New API key'))); await addAndVerifyApiKey(addApiKeyMock); }); }); @@ -165,13 +171,13 @@ describe('ApiKeysPage', () => { const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); addApiKeyMock.mockClear(); - userEvent.click(screen.getByRole('button', { name: /add api key/i })); + await userEvent.click(screen.getByRole('button', { name: /add api key/i })); await addAndVerifyApiKey(addApiKeyMock); - toggleShowExpired(); + await toggleShowExpired(); addApiKeyMock.mockClear(); - userEvent.click(screen.getByRole('button', { name: /add api key/i })); + await userEvent.click(screen.getByRole('button', { name: /add api key/i })); await addAndVerifyApiKey(addApiKeyMock); }); }); @@ -182,20 +188,20 @@ describe('ApiKeysPage', () => { const { addApiKeyMock } = setup({ apiKeys, apiKeysCount: apiKeys.length, hasFetched: true }); addApiKeyMock.mockClear(); - userEvent.click(screen.getByRole('button', { name: /add api key/i })); - userEvent.type(screen.getByPlaceholderText(/name/i), 'Test'); - userEvent.type(screen.getByPlaceholderText(/1d/i), '60x'); + await userEvent.click(screen.getByRole('button', { name: /add api key/i })); + await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test'); + await userEvent.type(screen.getByPlaceholderText(/1d/i), '60x'); expect(screen.queryByText(/not a valid duration/i)).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: /^add$/i })); + await userEvent.click(screen.getByRole('button', { name: /^add$/i })); expect(screen.getByText(/not a valid duration/i)).toBeInTheDocument(); expect(addApiKeyMock).toHaveBeenCalledTimes(0); }); }); }); -function toggleShowExpired() { +async function toggleShowExpired() { expect(screen.queryByLabelText(/include expired keys/i)).toBeInTheDocument(); - userEvent.click(screen.getByLabelText(/include expired keys/i)); + await userEvent.click(screen.getByLabelText(/include expired keys/i)); } async function addAndVerifyApiKey(addApiKeyMock: jest.Mock) { @@ -204,9 +210,9 @@ async function addAndVerifyApiKey(addApiKeyMock: jest.Mock) { expect(screen.getByPlaceholderText(/1d/i)).toBeInTheDocument(); expect(screen.getByRole('button', { name: /^add$/i })).toBeInTheDocument(); - userEvent.type(screen.getByPlaceholderText(/name/i), 'Test'); - userEvent.type(screen.getByPlaceholderText(/1d/i), '60s'); - userEvent.click(screen.getByRole('button', { name: /^add$/i })); + await userEvent.type(screen.getByPlaceholderText(/name/i), 'Test'); + await userEvent.type(screen.getByPlaceholderText(/1d/i), '60s'); + await userEvent.click(screen.getByRole('button', { name: /^add$/i })); expect(addApiKeyMock).toHaveBeenCalledTimes(1); expect(addApiKeyMock).toHaveBeenCalledWith({ name: 'Test', role: 'Viewer', secondsToLive: 60 }, expect.anything()); } diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index 6e3d8273275..4c95ac5db98 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -1,26 +1,33 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + // Utils -import { ApiKey, NewApiKey, StoreState } from 'app/types'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './state/selectors'; -import { addApiKey, deleteApiKey, loadApiKeys, toggleIncludeExpired } from './state/actions'; -import Page from 'app/core/components/Page/Page'; -import { ApiKeysAddedModal } from './ApiKeysAddedModal'; -import config from 'app/core/config'; +import { rangeUtil } from '@grafana/data'; +import { InlineField, InlineSwitch, VerticalGroup } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import { InlineField, InlineSwitch, VerticalGroup } from '@grafana/ui'; -import { rangeUtil } from '@grafana/data'; +import Page from 'app/core/components/Page/Page'; +import config from 'app/core/config'; +import { contextSrv } from 'app/core/core'; +import { getNavModel } from 'app/core/selectors/navModel'; import { getTimeZone } from 'app/features/profile/state/selectors'; -import { setSearchQuery } from './state/reducers'; -import { ApiKeysForm } from './ApiKeysForm'; -import { ApiKeysActionBar } from './ApiKeysActionBar'; -import { ApiKeysTable } from './ApiKeysTable'; -import { ApiKeysController } from './ApiKeysController'; +import { AccessControlAction, ApiKey, NewApiKey, StoreState } from 'app/types'; import { ShowModalReactEvent } from 'app/types/events'; +import { ApiKeysActionBar } from './ApiKeysActionBar'; +import { ApiKeysAddedModal } from './ApiKeysAddedModal'; +import { ApiKeysController } from './ApiKeysController'; +import { ApiKeysForm } from './ApiKeysForm'; +import { ApiKeysTable } from './ApiKeysTable'; +import { addApiKey, deleteApiKey, loadApiKeys, toggleIncludeExpired } from './state/actions'; +import { setSearchQuery } from './state/reducers'; +import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './state/selectors'; + function mapStateToProps(state: StoreState) { + const canRead = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysRead, true); + const canCreate = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysCreate, true); + const canDelete = contextSrv.hasAccess(AccessControlAction.ActionAPIKeysDelete, true); + return { navModel: getNavModel(state.navIndex, 'apikeys'), apiKeys: getApiKeys(state.apiKeys), @@ -30,6 +37,9 @@ function mapStateToProps(state: StoreState) { timeZone: getTimeZone(state.user), includeExpired: getIncludeExpired(state.apiKeys), includeExpiredDisabled: getIncludeExpiredDisabled(state.apiKeys), + canRead: canRead, + canCreate: canCreate, + canDelete: canDelete, }; } @@ -120,6 +130,9 @@ export class ApiKeysPageUnconnected extends PureComponent { timeZone, includeExpired, includeExpiredDisabled, + canRead, + canCreate, + canDelete, } = this.props; if (!hasFetched) { @@ -146,23 +159,35 @@ export class ApiKeysPageUnconnected extends PureComponent { onClick={toggleIsAdding} buttonTitle="New API key" proTip="Remember, you can provide view-only API access to other applications." + buttonDisabled={!canCreate} /> ) : null} {showTable ? ( ) : null} - + {showTable ? ( - + ) : null} diff --git a/public/app/features/api-keys/ApiKeysTable.tsx b/public/app/features/api-keys/ApiKeysTable.tsx index 06445bf09e1..b04f1e82a62 100644 --- a/public/app/features/api-keys/ApiKeysTable.tsx +++ b/public/app/features/api-keys/ApiKeysTable.tsx @@ -1,17 +1,20 @@ +import { css } from '@emotion/css'; import React, { FC } from 'react'; -import { DeleteButton, Icon, IconName, Tooltip, useTheme2 } from '@grafana/ui'; + import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; +import { DeleteButton, Icon, IconName, Tooltip, useTheme2 } from '@grafana/ui'; import { ApiKey } from '../../types'; -import { css } from '@emotion/css'; interface Props { apiKeys: ApiKey[]; timeZone: TimeZone; onDelete: (apiKey: ApiKey) => void; + canRead: boolean; + canDelete: boolean; } -export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete }) => { +export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete, canRead, canDelete }) => { const theme = useTheme2(); const styles = getStyles(theme); @@ -25,7 +28,7 @@ export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete }) => { - {apiKeys.length > 0 ? ( + {canRead && apiKeys.length > 0 ? ( {apiKeys.map((key) => { const isExpired = Boolean(key.expiration && Date.now() > new Date(key.expiration).getTime()); @@ -44,7 +47,12 @@ export const ApiKeysTable: FC = ({ apiKeys, timeZone, onDelete }) => { )} - onDelete(key)} /> + onDelete(key)} + disabled={!canDelete} + /> ); diff --git a/public/app/features/api-keys/state/actions.ts b/public/app/features/api-keys/state/actions.ts index 22fc829866a..467f45c6f77 100644 --- a/public/app/features/api-keys/state/actions.ts +++ b/public/app/features/api-keys/state/actions.ts @@ -1,5 +1,6 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; import { ApiKey, ThunkResult } from 'app/types'; + import { apiKeysLoaded, includeExpiredToggled, isFetching, setSearchQuery } from './reducers'; export function addApiKey(apiKey: ApiKey, openModal: (key: string) => void): ThunkResult { diff --git a/public/app/features/api-keys/state/reducers.test.ts b/public/app/features/api-keys/state/reducers.test.ts index 655a3d98c5b..b4048dd194f 100644 --- a/public/app/features/api-keys/state/reducers.test.ts +++ b/public/app/features/api-keys/state/reducers.test.ts @@ -1,4 +1,8 @@ -import { +import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { ApiKeysState } from '../../../types'; +import { getMultipleMockKeys } from '../__mocks__/apiKeysMock'; + +import { apiKeysLoaded, apiKeysReducer, includeExpiredToggled, @@ -6,9 +10,6 @@ isFetching, setSearchQuery, } from './reducers'; -import { getMultipleMockKeys } from '../__mocks__/apiKeysMock'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { ApiKeysState } from '../../../types'; describe('API Keys reducer', () => { it('should set keys', () => { diff --git a/public/app/features/api-keys/state/selectors.test.ts b/public/app/features/api-keys/state/selectors.test.ts index 307dadeba69..e1ba2f4d0f9 100644 --- a/public/app/features/api-keys/state/selectors.test.ts +++ b/public/app/features/api-keys/state/selectors.test.ts @@ -1,6 +1,8 @@ -import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './selectors'; +import { ApiKeysState } from 'app/types'; + import { getMultipleMockKeys } from '../__mocks__/apiKeysMock'; -import { ApiKeysState } from 'app/types'; + +import { getApiKeys, getApiKeysCount, getIncludeExpired, getIncludeExpiredDisabled } from './selectors'; describe('API Keys selectors', () => { const mockKeys = getMultipleMockKeys(5); diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index 90c6d4b650b..16c018e023b 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -1,9 +1,12 @@ import { ComponentType } from 'react'; + import { RegistryItem } from '@grafana/data'; -import { Anchor, BackgroundConfig, LineConfig, Placement } from './types'; -import { DimensionContext } from '../dimensions/context'; import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; +import { DimensionContext } from '../dimensions/context'; + +import { BackgroundConfig, Constraint, LineConfig, Placement } from './types'; + /** * This gets saved in panel json * @@ -19,7 +22,7 @@ export interface CanvasElementOptions { config?: TConfig; // Standard options available for all elements - anchor?: Anchor; // defaults top, left, width and height + constraint?: Constraint; // defaults vertical - top, horizontal - left placement?: Placement; background?: BackgroundConfig; border?: LineConfig; @@ -29,10 +32,6 @@ export interface CanvasElementProps { // Saved config config: TConfig; - // Calculated position info - width: number; - height: number; - // Raw data data?: TData; } @@ -53,6 +52,6 @@ export interface CanvasElementItem extends RegistryI getNewOptions: (options?: CanvasElementOptions) => Omit, 'type' | 'name'>; - /** Build the configuraiton UI */ + /** Build the configuration UI */ registerOptionsUI?: PanelOptionsSupplier>; } diff --git a/public/app/features/canvas/elements/button.tsx b/public/app/features/canvas/elements/button.tsx index 482a432b2b8..03f413012c4 100644 --- a/public/app/features/canvas/elements/button.tsx +++ b/public/app/features/canvas/elements/button.tsx @@ -1,12 +1,13 @@ import React, { PureComponent } from 'react'; -import { Button } from '@grafana/ui'; +import { Button } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions/context'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { TextDimensionConfig } from 'app/features/dimensions/types'; -import { CanvasElementItem, CanvasElementProps } from '../element'; import { APIEditor, APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/APIEditor'; +import { CanvasElementItem, CanvasElementProps } from '../element'; + interface ButtonData { text?: string; api?: APIEditorConfig; diff --git a/public/app/features/canvas/elements/droneFront.tsx b/public/app/features/canvas/elements/droneFront.tsx index bd5a1ef9348..8de1e1b7ae9 100644 --- a/public/app/features/canvas/elements/droneFront.tsx +++ b/public/app/features/canvas/elements/droneFront.tsx @@ -1,11 +1,12 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; - -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; -import { CanvasElementItem, CanvasElementProps } from '../element'; import { DimensionContext, ScalarDimensionConfig } from 'app/features/dimensions'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; + +import { CanvasElementItem, CanvasElementProps } from '../element'; interface DroneFrontData { rollAngle?: number; diff --git a/public/app/features/canvas/elements/droneSide.tsx b/public/app/features/canvas/elements/droneSide.tsx index a08577830bd..309f3673a55 100644 --- a/public/app/features/canvas/elements/droneSide.tsx +++ b/public/app/features/canvas/elements/droneSide.tsx @@ -1,11 +1,12 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; - -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; -import { CanvasElementItem, CanvasElementProps } from '../element'; import { DimensionContext, ScalarDimensionConfig } from 'app/features/dimensions'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; + +import { CanvasElementItem, CanvasElementProps } from '../element'; interface DroneSideData { pitchAngle?: number; diff --git a/public/app/features/canvas/elements/droneTop.tsx b/public/app/features/canvas/elements/droneTop.tsx index 5d8eb7f6214..abac83dee8c 100644 --- a/public/app/features/canvas/elements/droneTop.tsx +++ b/public/app/features/canvas/elements/droneTop.tsx @@ -1,11 +1,12 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; - -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; -import { CanvasElementItem, CanvasElementProps } from '../element'; import { DimensionContext, ScalarDimensionConfig } from 'app/features/dimensions'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; + +import { CanvasElementItem, CanvasElementProps } from '../element'; interface DroneTopData { bRightRotorRPM?: number; diff --git a/public/app/features/canvas/elements/icon.tsx b/public/app/features/canvas/elements/icon.tsx index 53652474c1e..fcce5a89429 100644 --- a/public/app/features/canvas/elements/icon.tsx +++ b/public/app/features/canvas/elements/icon.tsx @@ -1,20 +1,21 @@ +import { css } from '@emotion/css'; +import { isString } from 'lodash'; import React, { CSSProperties } from 'react'; +import SVG from 'react-inlinesvg'; -import { CanvasElementItem, CanvasElementProps } from '../element'; import { ColorDimensionConfig, ResourceDimensionConfig, ResourceDimensionMode, getPublicOrAbsoluteUrl, } from 'app/features/dimensions'; -import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors'; -import SVG from 'react-inlinesvg'; -import { css } from '@emotion/css'; -import { isString } from 'lodash'; -import { LineConfig } from '../types'; import { DimensionContext } from 'app/features/dimensions/context'; +import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors'; import { APIEditor, APIEditorConfig, callApi } from 'app/plugins/panel/canvas/editor/APIEditor'; +import { CanvasElementItem, CanvasElementProps } from '../element'; +import { LineConfig } from '../types'; + export interface IconConfig { path?: ResourceDimensionConfig; fill?: ColorDimensionConfig; @@ -38,7 +39,7 @@ const svgStrokePathClass = css` `; export function IconDisplay(props: CanvasElementProps) { - const { width, height, data } = props; + const { data } = props; if (!data?.path) { return null; } @@ -59,8 +60,6 @@ export function IconDisplay(props: CanvasElementProps) { @@ -78,6 +77,8 @@ export const iconItem: CanvasElementItem = { placement: { width: 50, height: 50, + top: 0, + left: 0, }, ...options, config: { diff --git a/public/app/features/canvas/elements/textBox.tsx b/public/app/features/canvas/elements/textBox.tsx index 477cebca0e7..e8f237a8183 100644 --- a/public/app/features/canvas/elements/textBox.tsx +++ b/public/app/features/canvas/elements/textBox.tsx @@ -1,14 +1,15 @@ +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; +import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; import { ColorDimensionConfig, TextDimensionConfig } from 'app/features/dimensions/types'; import { CanvasElementItem, CanvasElementProps } from '../element'; -import { css } from '@emotion/css'; -import { stylesFactory } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import { config } from 'app/core/config'; -import { DimensionContext } from 'app/features/dimensions/context'; export enum Align { Left = 'left', diff --git a/public/app/features/canvas/elements/windTurbine.tsx b/public/app/features/canvas/elements/windTurbine.tsx index e0b636ab41f..afcf65b39f8 100644 --- a/public/app/features/canvas/elements/windTurbine.tsx +++ b/public/app/features/canvas/elements/windTurbine.tsx @@ -1,11 +1,12 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; - -import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; -import { CanvasElementItem, CanvasElementProps } from '../element'; import { DimensionContext, ScalarDimensionConfig } from 'app/features/dimensions'; +import { ScalarDimensionEditor } from 'app/features/dimensions/editors'; + +import { CanvasElementItem, CanvasElementProps } from '../element'; interface WindTurbineData { rpm?: number; diff --git a/public/app/features/canvas/registry.ts b/public/app/features/canvas/registry.ts index 6bbf24f53c7..450d0cc480f 100644 --- a/public/app/features/canvas/registry.ts +++ b/public/app/features/canvas/registry.ts @@ -1,4 +1,5 @@ import { Registry } from '@grafana/data'; + import { CanvasElementItem, CanvasElementOptions } from './element'; import { buttonItem } from './elements/button'; import { droneFrontItem } from './elements/droneFront'; diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index 51e06c08b0d..37000ed02bd 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -1,18 +1,19 @@ import React, { CSSProperties } from 'react'; import { OnDrag, OnResize } from 'react-moveable/declaration/types'; +import { LayerElement } from 'app/core/components/Layers/types'; import { BackgroundImageSize, CanvasElementItem, CanvasElementOptions, canvasElementRegistry, - Placement, - Anchor, } from 'app/features/canvas'; -import { DimensionContext } from 'app/features/dimensions'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; +import { DimensionContext } from 'app/features/dimensions'; + +import { HorizontalConstraint, Placement, VerticalConstraint } from '../types'; + import { GroupState } from './group'; -import { LayerElement } from 'app/core/components/Layers/types'; import { Scene } from './scene'; let counter = 0; @@ -28,24 +29,19 @@ export class ElementState implements LayerElement { div?: HTMLDivElement; // Calculated - width = 100; - height = 100; data?: any; // depends on the type - // From options, but always set and always valid - anchor: Anchor; - placement: Placement; - constructor(public item: CanvasElementItem, public options: CanvasElementOptions, public parent?: GroupState) { const fallbackName = `Element ${Date.now()}`; if (!options) { this.options = { type: item.id, name: fallbackName }; } - this.anchor = options.anchor ?? {}; - this.placement = options.placement ?? {}; - options.anchor = this.anchor; - options.placement = this.placement; + options.constraint = options.constraint ?? { + vertical: VerticalConstraint.Top, + horizontal: HorizontalConstraint.Left, + }; + options.placement = options.placement ?? { width: 100, height: 100, top: 0, left: 0 }; const scene = this.getScene(); if (!options.name) { const newName = scene?.getNextElementName(); @@ -59,7 +55,6 @@ export class ElementState implements LayerElement { while (trav) { if (trav.isRoot()) { return trav.scene; - break; } trav = trav.parent; } @@ -71,72 +66,187 @@ export class ElementState implements LayerElement { return this.options.name; } - validatePlacement() { - const { anchor, placement } = this; - if (!(anchor.left || anchor.right)) { - anchor.left = true; - } - if (!(anchor.top || anchor.bottom)) { - anchor.top = true; - } + /** Use the configured options to update CSS style properties directly on the wrapper div **/ + applyLayoutStylesToDiv() { + const { constraint } = this.options; + const { vertical, horizontal } = constraint ?? {}; + const placement = this.options.placement ?? ({} as Placement); - const w = placement.width ?? 100; // this.div ? this.div.clientWidth : this.width; - const h = placement.height ?? 100; // this.div ? this.div.clientHeight : this.height; - - if (anchor.top) { - if (!placement.top) { - placement.top = 0; - } - if (anchor.bottom) { - delete placement.height; - } else { - placement.height = h; - delete placement.bottom; - } - } else if (anchor.bottom) { - if (!placement.bottom) { - placement.bottom = 0; - } - placement.height = h; - delete placement.top; - } - - if (anchor.left) { - if (!placement.left) { - placement.left = 0; - } - if (anchor.right) { - delete placement.width; - } else { - placement.width = w; - delete placement.right; - } - } else if (anchor.right) { - if (!placement.right) { - placement.right = 0; - } - placement.width = w; - delete placement.left; - } - - this.width = w; - this.height = h; - - this.options.anchor = this.anchor; - this.options.placement = this.placement; - } - - // The parent size, need to set our own size based on offsets - updateSize(width: number, height: number) { - this.width = width; - this.height = height; - this.validatePlacement(); - - // Update the CSS position - this.sizeStyle = { - ...this.options.placement, + const style: React.CSSProperties = { position: 'absolute', }; + + const translate = ['0px', '0px']; + + switch (vertical) { + case VerticalConstraint.Top: + placement.top = placement.top ?? 0; + placement.height = placement.height ?? 100; + style.top = `${placement.top}px`; + style.height = `${placement.height}px`; + delete placement.bottom; + break; + case VerticalConstraint.Bottom: + placement.bottom = placement.bottom ?? 0; + placement.height = placement.height ?? 100; + style.bottom = `${placement.bottom}px`; + style.height = `${placement.height}px`; + delete placement.top; + break; + case VerticalConstraint.TopBottom: + placement.top = placement.top ?? 0; + placement.bottom = placement.bottom ?? 0; + style.top = `${placement.top}px`; + style.bottom = `${placement.bottom}px`; + delete placement.height; + break; + case VerticalConstraint.Center: + placement.top = placement.top ?? 0; + placement.height = placement.height ?? 100; + translate[1] = '-50%'; + style.top = `calc(50% - ${placement.top}px)`; + style.height = `${placement.height}px`; + delete placement.bottom; + break; + case VerticalConstraint.Scale: + placement.top = placement.top ?? 0; + placement.bottom = placement.bottom ?? 0; + style.top = `${placement.top}%`; + style.bottom = `${placement.bottom}%`; + delete placement.height; + break; + } + + switch (horizontal) { + case HorizontalConstraint.Left: + placement.left = placement.left ?? 0; + placement.width = placement.width ?? 100; + style.left = `${placement.left}px`; + style.width = `${placement.width}px`; + delete placement.right; + break; + case HorizontalConstraint.Right: + placement.right = placement.right ?? 0; + placement.width = placement.width ?? 100; + style.right = `${placement.right}px`; + style.width = `${placement.width}px`; + delete placement.left; + break; + case HorizontalConstraint.LeftRight: + placement.left = placement.left ?? 0; + placement.right = placement.right ?? 0; + style.left = `${placement.left}px`; + style.right = `${placement.right}px`; + delete placement.width; + break; + case HorizontalConstraint.Center: + placement.left = placement.left ?? 0; + placement.width = placement.width ?? 100; + translate[0] = '-50%'; + style.left = `calc(50% - ${placement.left}px)`; + style.width = `${placement.width}px`; + delete placement.right; + break; + case HorizontalConstraint.Scale: + placement.left = placement.left ?? 0; + placement.right = placement.right ?? 0; + style.left = `${placement.left}%`; + style.right = `${placement.right}%`; + delete placement.width; + break; + } + + style.transform = `translate(${translate[0]}, ${translate[1]})`; + this.options.placement = placement; + this.sizeStyle = style; + if (this.div) { + for (const key in this.sizeStyle) { + this.div.style[key as any] = (this.sizeStyle as any)[key]; + } + + for (const key in this.dataStyle) { + this.div.style[key as any] = (this.dataStyle as any)[key]; + } + } + } + + setPlacementFromConstraint() { + const { constraint } = this.options; + const { vertical, horizontal } = constraint ?? {}; + + const elementContainer = this.div && this.div.getBoundingClientRect(); + const parentContainer = this.div && this.div.parentElement?.getBoundingClientRect(); + + const relativeTop = + elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.top - parentContainer.top)) : 0; + const relativeBottom = + elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.bottom - parentContainer.bottom)) : 0; + const relativeLeft = + elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.left - parentContainer.left)) : 0; + const relativeRight = + elementContainer && parentContainer ? Math.abs(Math.round(elementContainer.right - parentContainer.right)) : 0; + + const placement = {} as Placement; + + const width = elementContainer?.width ?? 100; + const height = elementContainer?.height ?? 100; + + switch (vertical) { + case VerticalConstraint.Top: + placement.top = relativeTop; + placement.height = height; + break; + case VerticalConstraint.Bottom: + placement.bottom = relativeBottom; + placement.height = height; + break; + case VerticalConstraint.TopBottom: + placement.top = relativeTop; + placement.bottom = relativeBottom; + break; + case VerticalConstraint.Center: + const elementCenter = elementContainer ? relativeTop + height / 2 : 0; + const parentCenter = parentContainer ? parentContainer.height / 2 : 0; + const distanceFromCenter = parentCenter - elementCenter; + placement.top = distanceFromCenter; + placement.height = height; + break; + case VerticalConstraint.Scale: + placement.top = (relativeTop / (parentContainer?.height ?? height)) * 100; + placement.bottom = (relativeBottom / (parentContainer?.height ?? height)) * 100; + break; + } + + switch (horizontal) { + case HorizontalConstraint.Left: + placement.left = relativeLeft; + placement.width = width; + break; + case HorizontalConstraint.Right: + placement.right = relativeRight; + placement.width = width; + break; + case HorizontalConstraint.LeftRight: + placement.left = relativeLeft; + placement.right = relativeRight; + break; + case HorizontalConstraint.Center: + const elementCenter = elementContainer ? relativeLeft + width / 2 : 0; + const parentCenter = parentContainer ? parentContainer.width / 2 : 0; + const distanceFromCenter = parentCenter - elementCenter; + placement.left = distanceFromCenter; + placement.width = width; + break; + case HorizontalConstraint.Scale: + placement.left = (relativeLeft / (parentContainer?.width ?? width)) * 100; + placement.right = (relativeRight / (parentContainer?.width ?? width)) * 100; + break; + } + + this.options.placement = placement; + + this.applyLayoutStylesToDiv(); + this.revId++; } updateData(ctx: DimensionContext) { @@ -236,37 +346,24 @@ export class ElementState implements LayerElement { initElement = (target: HTMLDivElement) => { this.div = target; + this.applyLayoutStylesToDiv(); }; applyDrag = (event: OnDrag) => { - const { placement, anchor } = this; - - const deltaX = event.delta[0]; - const deltaY = event.delta[1]; - - const style = event.target.style; - if (anchor.top) { - placement.top! += deltaY; - style.top = `${placement.top}px`; - } - if (anchor.bottom) { - placement.bottom! -= deltaY; - style.bottom = `${placement.bottom}px`; - } - if (anchor.left) { - placement.left! += deltaX; - style.left = `${placement.left}px`; - } - if (anchor.right) { - placement.right! -= deltaX; - style.right = `${placement.right}px`; - } + event.target.style.transform = event.transform; }; // kinda like: // https://github.com/grafana/grafana-edge-app/blob/main/src/panels/draw/WrapItem.tsx#L44 applyResize = (event: OnResize) => { - const { placement, anchor } = this; + const { options } = this; + const { placement, constraint } = options; + const { vertical, horizontal } = constraint ?? {}; + + const top = vertical === VerticalConstraint.Top || vertical === VerticalConstraint.TopBottom; + const bottom = vertical === VerticalConstraint.Bottom || vertical === VerticalConstraint.TopBottom; + const left = horizontal === HorizontalConstraint.Left || horizontal === HorizontalConstraint.LeftRight; + const right = horizontal === HorizontalConstraint.Right || horizontal === HorizontalConstraint.LeftRight; const style = event.target.style; const deltaX = event.delta[0]; @@ -275,69 +372,60 @@ export class ElementState implements LayerElement { const dirTB = event.direction[1]; if (dirLR === 1) { // RIGHT - if (anchor.right) { - placement.right! -= deltaX; - style.right = `${placement.right}px`; - if (!anchor.left) { - placement.width = event.width; - style.width = `${placement.width}px`; + if (right) { + placement!.right! -= deltaX; + style.right = `${placement!.right}px`; + if (!left) { + placement!.width = event.width; + style.width = `${placement!.width}px`; } } else { - placement.width! = event.width; - style.width = `${placement.width}px`; + placement!.width! = event.width; + style.width = `${placement!.width}px`; } } else if (dirLR === -1) { // LEFT - if (anchor.left) { - placement.left! -= deltaX; - placement.width! = event.width; - style.left = `${placement.left}px`; - style.width = `${placement.width}px`; + if (left) { + placement!.left! -= deltaX; + placement!.width! = event.width; + style.left = `${placement!.left}px`; + style.width = `${placement!.width}px`; } else { - placement.width! += deltaX; - style.width = `${placement.width}px`; + placement!.width! += deltaX; + style.width = `${placement!.width}px`; } } if (dirTB === -1) { // TOP - if (anchor.top) { - placement.top! -= deltaY; - placement.height = event.height; - style.top = `${placement.top}px`; - style.height = `${placement.height}px`; + if (top) { + placement!.top! -= deltaY; + placement!.height = event.height; + style.top = `${placement!.top}px`; + style.height = `${placement!.height}px`; } else { - placement.height = event.height; - style.height = `${placement.height}px`; + placement!.height = event.height; + style.height = `${placement!.height}px`; } } else if (dirTB === 1) { // BOTTOM - if (anchor.bottom) { - placement.bottom! -= deltaY; - placement.height! = event.height; - style.bottom = `${placement.bottom}px`; - style.height = `${placement.height}px`; + if (bottom) { + placement!.bottom! -= deltaY; + placement!.height! = event.height; + style.bottom = `${placement!.bottom}px`; + style.height = `${placement!.height}px`; } else { - placement.height! = event.height; - style.height = `${placement.height}px`; + placement!.height! = event.height; + style.height = `${placement!.height}px`; } } - - this.width = event.width; - this.height = event.height; }; render() { const { item } = this; return ( -
- +
+
); } diff --git a/public/app/features/canvas/runtime/group.tsx b/public/app/features/canvas/runtime/group.tsx index d331d745f9c..91e1c6a6510 100644 --- a/public/app/features/canvas/runtime/group.tsx +++ b/public/app/features/canvas/runtime/group.tsx @@ -1,13 +1,17 @@ -import React from 'react'; -import { CanvasGroupOptions, canvasElementRegistry } from 'app/features/canvas'; -import { DimensionContext } from 'app/features/dimensions'; -import { notFoundItem } from 'app/features/canvas/elements/notFound'; -import { ElementState } from './element'; -import { CanvasElementItem } from '../element'; -import { LayerActionID } from 'app/plugins/panel/canvas/types'; import { cloneDeep } from 'lodash'; -import { Scene } from './scene'; +import React from 'react'; + +import { CanvasGroupOptions, canvasElementRegistry } from 'app/features/canvas'; +import { notFoundItem } from 'app/features/canvas/elements/notFound'; +import { DimensionContext } from 'app/features/dimensions'; +import { LayerActionID } from 'app/plugins/panel/canvas/types'; + +import { CanvasElementItem } from '../element'; +import { HorizontalConstraint, Placement, VerticalConstraint } from '../types'; + +import { ElementState } from './element'; import { RootElement } from './root'; +import { Scene } from './scene'; export const groupItemDummy: CanvasElementItem = { id: 'group', @@ -53,27 +57,6 @@ export class GroupState extends ElementState { return false; } - // The parent size, need to set our own size based on offsets - updateSize(width: number, height: number) { - super.updateSize(width, height); - if (!this.parent) { - this.width = width; - this.height = height; - this.sizeStyle.width = width; - this.sizeStyle.height = height; - } - - // Update children with calculated size - for (const elem of this.elements) { - elem.updateSize(this.width, this.height); - } - - // The group forced to full width (for now) - this.sizeStyle.width = width; - this.sizeStyle.height = height; - this.sizeStyle.position = 'absolute'; - } - updateData(ctx: DimensionContext) { super.updateData(ctx); for (const elem of this.elements) { @@ -113,21 +96,50 @@ export class GroupState extends ElementState { return; } const opts = cloneDeep(element.options); - if (element.anchor.top) { - opts.placement!.top! += 10; - } - if (element.anchor.left) { - opts.placement!.left! += 10; - } - if (element.anchor.bottom) { - opts.placement!.bottom! += 10; - } - if (element.anchor.right) { - opts.placement!.right! += 10; + + const { constraint, placement: oldPlacement } = element.options; + const { vertical, horizontal } = constraint ?? {}; + const placement = oldPlacement ?? ({} as Placement); + + switch (vertical) { + case VerticalConstraint.Top: + case VerticalConstraint.TopBottom: + if (placement.top == null) { + placement.top = 25; + } else { + placement.top += 10; + } + break; + case VerticalConstraint.Bottom: + if (placement.bottom == null) { + placement.bottom = 100; + } else { + placement.bottom -= 10; + } + break; } + switch (horizontal) { + case HorizontalConstraint.Left: + case HorizontalConstraint.LeftRight: + if (placement.left == null) { + placement.left = 50; + } else { + placement.left += 10; + } + break; + case HorizontalConstraint.Right: + if (placement.right == null) { + placement.right = 50; + } else { + placement.right -= 10; + } + break; + } + + opts.placement = placement; + const copy = new ElementState(element.item, opts, this); - copy.updateSize(element.width, element.height); copy.updateData(this.scene.context); if (updateName) { copy.options.name = this.scene.getNextElementName(); diff --git a/public/app/features/canvas/runtime/root.tsx b/public/app/features/canvas/runtime/root.tsx index 1106adddd93..8eebe0c01aa 100644 --- a/public/app/features/canvas/runtime/root.tsx +++ b/public/app/features/canvas/runtime/root.tsx @@ -1,25 +1,22 @@ import { CanvasGroupOptions, CanvasElementOptions } from 'app/features/canvas'; + import { GroupState } from './group'; import { Scene } from './scene'; export class RootElement extends GroupState { constructor(public options: CanvasGroupOptions, public scene: Scene, private changeCallback: () => void) { super(options, scene); + + this.sizeStyle = { + height: '100%', + width: '100%', + }; } isRoot(): this is RootElement { return true; } - // The parent size is always fullsize - updateSize(width: number, height: number) { - super.updateSize(width, height); - this.width = width; - this.height = height; - this.sizeStyle.width = width; - this.sizeStyle.height = height; - } - // root type can not change onChange(options: CanvasElementOptions) { this.revId++; @@ -28,10 +25,10 @@ export class RootElement extends GroupState { } getSaveModel(): CanvasGroupOptions { - const { placement, anchor, ...rest } = this.options; + const { placement, constraint, ...rest } = this.options; return { - ...rest, // everything except placement & anchor + ...rest, // everything except placement & constraint elements: this.elements.map((v) => v.getSaveModel()), }; } diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index da2ad559db8..0db06d31aff 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -1,14 +1,14 @@ -import React, { CSSProperties } from 'react'; import { css } from '@emotion/css'; +import Moveable from 'moveable'; +import React, { CSSProperties } from 'react'; import { ReplaySubject, Subject } from 'rxjs'; import { first } from 'rxjs/operators'; -import Moveable from 'moveable'; import Selecto from 'selecto'; -import { config } from 'app/core/config'; import { GrafanaTheme2, PanelData } from '@grafana/data'; import { stylesFactory } from '@grafana/ui'; -import { Anchor, CanvasGroupOptions, DEFAULT_CANVAS_ELEMENT_CONFIG, Placement } from 'app/features/canvas'; +import { config } from 'app/core/config'; +import { CanvasGroupOptions, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas'; import { ColorDimensionConfig, ResourceDimensionConfig, @@ -24,11 +24,12 @@ import { getTextDimensionFromData, getScalarDimensionFromData, } from 'app/features/dimensions/utils'; -import { ElementState } from './element'; -import { RootElement } from './root'; -import { GroupState } from './group'; import { LayerActionID } from 'app/plugins/panel/canvas/types'; +import { ElementState } from './element'; +import { GroupState } from './group'; +import { RootElement } from './root'; + export interface SelectionParams { targets: Array; group?: GroupState; @@ -112,7 +113,6 @@ export class Scene { this.width = width; this.height = height; this.style = { width, height }; - this.root.updateSize(width, height); if (this.selecto?.getSelectedTargets().length) { this.clearCurrentSelection(); @@ -157,44 +157,16 @@ export class Scene { this.save(); } - toggleAnchor(element: ElementState, k: keyof Anchor) { - const { div } = element; - if (!div) { - console.log('Not ready'); - return; - } - - const w = element.parent?.width ?? 100; - const h = element.parent?.height ?? 100; - - // Get computed position.... - const info = div.getBoundingClientRect(); // getElementInfo(div, element.parent?.div); - console.log('DIV info', div); - - const placement: Placement = { - top: info.top, - left: info.left, - width: info.width, - height: info.height, - bottom: h - info.bottom, - right: w - info.right, - }; - - console.log('PPP', placement); - - // // TODO: needs to recalculate placement based on absolute values... - // element.anchor[k] = !Boolean(element.anchor[k]); - // element.placement = placement; - // element.validatePlacement(); - // element.revId++; - // this.revId++; - // this.save(); - - this.moved.next(Date.now()); - } - - save = () => { + save = (updateMoveable = false) => { this.onSave(this.root.getSaveModel()); + + if (updateMoveable) { + setTimeout(() => { + if (this.div) { + this.initMoveable(true); + } + }, 100); + } }; private findElementByTarget = (target: HTMLElement | SVGElement): ElementState | undefined => { @@ -262,8 +234,8 @@ export class Scene { initMoveable = (destroySelecto = false, allowChanges = true) => { const targetElements = this.generateTargetElements(this.root.elements); - if (destroySelecto) { - this.selecto?.destroy(); + if (destroySelecto && this.selecto) { + this.selecto.destroy(); } this.selecto = new Selecto({ @@ -283,22 +255,20 @@ export class Scene { .on('drag', (event) => { const targetedElement = this.findElementByTarget(event.target); targetedElement!.applyDrag(event); - this.moved.next(Date.now()); // TODO only on end }) .on('dragGroup', (e) => { e.events.forEach((event) => { const targetedElement = this.findElementByTarget(event.target); targetedElement!.applyDrag(event); }); - this.moved.next(Date.now()); // TODO only on end }) .on('dragEnd', (event) => { const targetedElement = this.findElementByTarget(event.target); - - if (targetedElement && targetedElement.parent) { - const parent = targetedElement.parent; - targetedElement.updateSize(parent.width, parent.height); + if (targetedElement) { + targetedElement?.setPlacementFromConstraint(); } + + this.moved.next(Date.now()); }) .on('resize', (event) => { const targetedElement = this.findElementByTarget(event.target); @@ -311,6 +281,13 @@ export class Scene { targetedElement!.applyResize(event); }); this.moved.next(Date.now()); // TODO only on end + }) + .on('resizeEnd', (event) => { + const targetedElement = this.findElementByTarget(event.target); + + if (targetedElement) { + targetedElement?.setPlacementFromConstraint(); + } }); let targets: Array = []; diff --git a/public/app/features/canvas/types.ts b/public/app/features/canvas/types.ts index 872e149cf6b..54055e1be1f 100644 --- a/public/app/features/canvas/types.ts +++ b/public/app/features/canvas/types.ts @@ -10,11 +10,25 @@ export interface Placement { height?: number; } -export interface Anchor { - top?: boolean; - left?: boolean; - right?: boolean; - bottom?: boolean; +export interface Constraint { + horizontal?: HorizontalConstraint; + vertical?: VerticalConstraint; +} + +export enum HorizontalConstraint { + Left = 'left', + Right = 'right', + LeftRight = 'leftright', + Center = 'center', + Scale = 'scale', +} + +export enum VerticalConstraint { + Top = 'top', + Bottom = 'bottom', + TopBottom = 'topbottom', + Center = 'center', + Scale = 'scale', } export enum BackgroundImageSize { diff --git a/public/app/features/commandPalette/CommandPalette.tsx b/public/app/features/commandPalette/CommandPalette.tsx new file mode 100644 index 00000000000..db0e978be14 --- /dev/null +++ b/public/app/features/commandPalette/CommandPalette.tsx @@ -0,0 +1,149 @@ +import { css } from '@emotion/css'; +import { + KBarAnimator, + KBarPortal, + KBarPositioner, + KBarResults, + KBarSearch, + useMatches, + Action, + VisualState, + useRegisterActions, + useKBar, +} from 'kbar'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { reportInteraction, locationService } from '@grafana/runtime'; +import { useStyles2 } from '@grafana/ui'; +import { StoreState } from 'app/types'; + +import { keybindingSrv } from '../../core/services/keybindingSrv'; + +import { ResultItem } from './ResultItem'; +import getDashboardNavActions from './actions/dashboard.nav.actions'; +import getGlobalActions from './actions/global.static.actions'; + +/** + * Wrap all the components from KBar here. + * @constructor + */ + +export const CommandPalette = () => { + const styles = useStyles2(getSearchStyles); + const [actions, setActions] = useState([]); + const { notHidden, query, showing } = useKBar((state) => ({ + notHidden: state.visualState !== VisualState.hidden, + showing: state.visualState === VisualState.showing, + })); + const isNotLogin = locationService.getLocation().pathname !== '/login'; + + const { navBarTree } = useSelector((state: StoreState) => { + return { + navBarTree: state.navBarTree, + }; + }); + + keybindingSrv.bind('esc', () => { + if (notHidden) { + query.setVisualState(VisualState.animatingOut); + } + }); + + useEffect(() => { + (async () => { + if (isNotLogin) { + const staticActions = getGlobalActions(navBarTree); + const dashAct = await getDashboardNavActions('go/dashboard'); + setActions([...staticActions, ...dashAct]); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isNotLogin]); + + useEffect(() => { + if (showing) { + reportInteraction('commandPalette_opened'); + } + }, [showing]); + + useRegisterActions(actions, [actions]); + + return actions.length > 0 ? ( + + + + + + + + + ) : null; +}; + +const RenderResults = () => { + const { results, rootActionId } = useMatches(); + const styles = useStyles2(getSearchStyles); + + return ( +
+ + typeof item === 'string' ? ( +
{item}
+ ) : ( + + ) + } + /> +
+ ); +}; + +const getSearchStyles = (theme: GrafanaTheme2) => ({ + positioner: css({ + zIndex: theme.zIndex.portal, + marginTop: '0px', + '&::before': { + content: '""', + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, + background: theme.components.overlay.background, + backdropFilter: 'blur(1px)', + }, + }), + animator: css({ + maxWidth: theme.breakpoints.values.sm, // supposed to be 600... + width: '100%', + background: theme.colors.background.canvas, + color: theme.colors.text.primary, + borderRadius: theme.shape.borderRadius(4), + overflow: 'hidden', + boxShadow: theme.shadows.z3, + }), + search: css({ + padding: theme.spacing(2, 3), + fontSize: theme.typography.fontSize, + width: '100%', + boxSizing: 'border-box', + outline: 'none', + border: 'none', + background: theme.colors.background.canvas, + color: theme.colors.text.primary, + borderBottom: `1px solid ${theme.colors.border.weak}`, + }), + sectionHeader: css({ + padding: theme.spacing(1, 2), + fontSize: theme.typography.h6.fontSize, + fontWeight: theme.typography.body.fontWeight, + color: theme.colors.text.secondary, + }), + resultsContainer: css({ + padding: theme.spacing(2, 0), + }), +}); diff --git a/public/app/features/commandPalette/ResultItem.tsx b/public/app/features/commandPalette/ResultItem.tsx new file mode 100644 index 00000000000..6f817fac456 --- /dev/null +++ b/public/app/features/commandPalette/ResultItem.tsx @@ -0,0 +1,124 @@ +import { css } from '@emotion/css'; +import { ActionId, ActionImpl } from 'kbar'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; + +export const ResultItem = React.forwardRef( + ( + { + action, + active, + currentRootActionId, + }: { + action: ActionImpl; + active: boolean; + currentRootActionId: ActionId; + }, + ref: React.Ref + ) => { + const ancestors = React.useMemo(() => { + if (!currentRootActionId) { + return action.ancestors; + } + + const index = action.ancestors.findIndex((ancestor) => ancestor.id === currentRootActionId); + // +1 removes the currentRootAction; e.g. + // if we are on the "Set theme" parent action, + // the UI should not display "Set theme… > Dark" + // but rather just "Dark" + return action.ancestors.slice(index + 1); + }, [action.ancestors, currentRootActionId]); + + const theme = useTheme2(); + const styles = getResultItemStyles(theme, active); + + return ( +
+
+ {action.icon} +
+
+ {ancestors.length > 0 && + ancestors.map((ancestor) => ( + + {ancestor.name} + › + + ))} + {action.name} +
+
+ {action.subtitle && {action.subtitle}} +
+ {action.shortcut?.length ? ( +
+ {action.shortcut.map((sc) => ( + + {sc} + + ))} +
+ ) : null} +
+ ); + } +); +ResultItem.displayName = 'ResultItem'; + +const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => { + const textColor = isActive ? theme.colors.text.maxContrast : theme.colors.text.primary; + const rowBackgroundColor = isActive ? theme.colors.background.primary : 'transparent'; + const shortcutBackgroundColor = isActive ? theme.colors.background.secondary : theme.colors.background.primary; + return { + row: css({ + color: textColor, + padding: theme.spacing(1, 2), + background: rowBackgroundColor, + display: 'flex', + alightItems: 'center', + justifyContent: 'space-between', + cursor: 'pointer', + '&:before': { + display: isActive ? 'block' : 'none', + content: '" "', + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + width: theme.spacing(0.5), + borderRadius: theme.shape.borderRadius(1), + backgroundImage: theme.colors.gradients.brandVertical, + }, + }), + actionContainer: css({ + display: 'flex', + gap: theme.spacing(2), + alignitems: 'center', + fontsize: theme.typography.fontSize, + }), + textContainer: css({ + display: 'flex', + flexDirection: 'column', + }), + shortcut: css({ + padding: theme.spacing(0, 1), + background: shortcutBackgroundColor, + borderRadius: theme.shape.borderRadius(), + fontsize: theme.typography.fontSize, + }), + breadcrumbAncestor: css({ + opacity: 0.5, + marginRight: theme.spacing(1), + }), + subtitleText: css({ + fontSize: theme.typography.fontSize - 2, + }), + shortcutContainer: css({ + display: 'grid', + gridAutoFlow: 'column', + gap: theme.spacing(1), + }), + }; +}; diff --git a/public/app/features/commandPalette/actions/dashboard.nav.actions.ts b/public/app/features/commandPalette/actions/dashboard.nav.actions.ts new file mode 100644 index 00000000000..703b928d47c --- /dev/null +++ b/public/app/features/commandPalette/actions/dashboard.nav.actions.ts @@ -0,0 +1,25 @@ +import { Action } from 'kbar'; + +import { locationService, getBackendSrv } from '@grafana/runtime'; + +async function getDashboardNav(parentId: string): Promise { + const data: Array<{ type: string; title: string; url: string }> = await getBackendSrv().get('/api/search'); + + const goToDashboardActions: Action[] = data + .filter((item) => item.type === 'dash-db') + .map((item) => ({ + parent: parentId, + id: `go/dashboard/${item.url}`, + name: `Go to dashboard ${item.title}`, + perform: () => { + locationService.push(item.url); + }, + })); + + return goToDashboardActions; +} + +export default async (parentId: string) => { + const dashboardNav = await getDashboardNav(parentId); + return dashboardNav; +}; diff --git a/public/app/features/commandPalette/actions/global.static.actions.ts b/public/app/features/commandPalette/actions/global.static.actions.ts new file mode 100644 index 00000000000..ba4e745b039 --- /dev/null +++ b/public/app/features/commandPalette/actions/global.static.actions.ts @@ -0,0 +1,153 @@ +import { Action, Priority } from 'kbar'; + +import { NavModelItem } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; + +export default (navBarTree: NavModelItem[]) => { + const globalActions: Action[] = [ + { + id: 'go/search', + name: 'Go to dashboard search', + keywords: 'navigate', + perform: () => locationService.push('?search=open'), + section: 'Navigation', + shortcut: ['s', 'o'], + }, + { + id: 'go/dashboard', + name: 'Go to dashboard...', + keywords: 'navigate', + section: 'Navigation', + priority: Priority.NORMAL, + }, + { + id: 'preferences/theme', + name: 'Change theme...', + keywords: 'interface color dark light', + section: 'Preferences', + }, + { + id: 'preferences/dark-theme', + name: 'Dark', + keywords: 'dark theme', + section: '', + perform: () => { + locationService.push({ search: '?theme=dark' }); + location.reload(); + }, + parent: 'preferences/theme', + }, + { + id: 'preferences/light-theme', + name: 'Light', + keywords: 'light theme', + section: '', + perform: () => { + locationService.push({ search: '?theme=light' }); + location.reload(); + }, + parent: 'preferences/theme', + }, + ]; + + // this maps actions to navbar by URL items for showing/hiding + // actions is an array for multiple child actions that would be under one navbar item + const navBarActionMap = [ + { + url: '/dashboard/new', + actions: [ + { + id: 'management/create-folder', + name: 'Create folder', + keywords: 'new add', + perform: () => locationService.push('/dashboards/folder/new'), + section: 'Management', + }, + { + id: 'management/create-dashboard', + name: 'Create dashboard', + keywords: 'new add', + perform: () => locationService.push('/dashboard/new'), + section: 'Management', + }, + ], + }, + { + url: '/', + actions: [ + { + id: 'go/home', + name: 'Go to home', + keywords: 'navigate', + perform: () => locationService.push('/'), + section: 'Navigation', + shortcut: ['g', 'h'], + priority: Priority.HIGH, + }, + ], + }, + { + url: '/explore', + actions: [ + { + id: 'go/explore', + name: 'Go to explore', + keywords: 'navigate', + perform: () => locationService.push('/explore'), + section: 'Navigation', + priority: Priority.NORMAL, + }, + ], + }, + { + url: '/alerting', + actions: [ + { + id: 'go/alerting', + name: 'Go to alerting', + keywords: 'navigate notification', + perform: () => locationService.push('/alerting'), + section: 'Navigation', + priority: Priority.NORMAL, + }, + ], + }, + { + url: '/profile', + actions: [ + { + id: 'go/profile', + name: 'Go to profile', + keywords: 'navigate preferences', + perform: () => locationService.push('/profile'), + section: 'Navigation', + shortcut: ['g', 'p'], + priority: Priority.LOW, + }, + ], + }, + { + url: '/datasources', + actions: [ + { + id: 'go/configuration', + name: 'Go to data sources configuration', + keywords: 'navigate settings ds', + perform: () => locationService.push('/datasources'), + section: 'Navigation', + }, + ], + }, + ]; + + const navBarActions: Action[] = []; + + navBarActionMap.forEach((navBarAction) => { + const navBarItem = navBarTree.find((navBarItem) => navBarItem.url === navBarAction.url); + if (navBarItem && !navBarItem.hideFromNavbar) { + navBarActions.push(...navBarAction.actions); + } + }); + + return [...globalActions, ...navBarActions]; +}; diff --git a/public/app/features/comments/Comment.tsx b/public/app/features/comments/Comment.tsx index 7f78a65e4b3..6731fe3a656 100644 --- a/public/app/features/comments/Comment.tsx +++ b/public/app/features/comments/Comment.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { GrafanaTheme2, renderMarkdown } from '@grafana/data'; import { css } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; import DangerouslySetHtmlContent from 'dangerously-set-html-content'; +import React from 'react'; + +import { GrafanaTheme2, renderMarkdown } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; import { Message } from './types'; diff --git a/public/app/features/comments/CommentManager.tsx b/public/app/features/comments/CommentManager.tsx index 3c9fce75fdb..c8d987d4885 100644 --- a/public/app/features/comments/CommentManager.tsx +++ b/public/app/features/comments/CommentManager.tsx @@ -1,7 +1,8 @@ import React, { PureComponent } from 'react'; +import { Unsubscribable } from 'rxjs'; + import { isLiveChannelMessageEvent, LiveChannelScope } from '@grafana/data'; import { getBackendSrv, getGrafanaLiveSrv } from '@grafana/runtime'; -import { Unsubscribable } from 'rxjs'; import { CommentView } from './CommentView'; import { Message, MessagePacket } from './types'; diff --git a/public/app/features/comments/CommentView.tsx b/public/app/features/comments/CommentView.tsx index 630a1621d05..a041050ae1b 100644 --- a/public/app/features/comments/CommentView.tsx +++ b/public/app/features/comments/CommentView.tsx @@ -1,5 +1,6 @@ -import React, { FormEvent, useLayoutEffect, useRef, useState } from 'react'; import { css } from '@emotion/css'; +import React, { FormEvent, useLayoutEffect, useRef, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { CustomScrollbar, TextArea, useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx index 4ce64244bd9..c52534ca3b0 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { AddPanelWidgetUnconnected as AddPanelWidget, Props } from './AddPanelWidget'; +import React from 'react'; + import { DashboardModel, PanelModel } from '../../state'; +import { AddPanelWidgetUnconnected as AddPanelWidget, Props } from './AddPanelWidget'; + const getTestContext = (propOverrides?: object) => { const props: Props = { dashboard: {} as DashboardModel, diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index a059a67818d..b3cc87dd168 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -1,25 +1,26 @@ -import React, { useMemo, useState } from 'react'; -import { connect, MapDispatchToProps } from 'react-redux'; import { css, cx, keyframes } from '@emotion/css'; import { chain, cloneDeep, defaults, find, sortBy } from 'lodash'; +import React, { useMemo, useState } from 'react'; +import { connect, MapDispatchToProps } from 'react-redux'; import tinycolor from 'tinycolor2'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { locationService, reportInteraction } from '@grafana/runtime'; import { Icon, IconButton, useStyles2 } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; -import { GrafanaTheme2 } from '@grafana/data'; - +import { CardButton } from 'app/core/components/CardButton'; import config from 'app/core/config'; +import { LS_PANEL_COPY_KEY } from 'app/core/constants'; import store from 'app/core/store'; import { addPanel } from 'app/features/dashboard/state/reducers'; -import { DashboardModel, PanelModel } from '../../state'; -import { LS_PANEL_COPY_KEY } from 'app/core/constants'; -import { LibraryElementDTO } from '../../../library-panels/types'; -import { toPanelModelLibraryPanel } from '../../../library-panels/utils'; + import { LibraryPanelsSearch, LibraryPanelsSearchVariant, } from '../../../library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch'; -import { CardButton } from 'app/core/components/CardButton'; +import { LibraryElementDTO } from '../../../library-panels/types'; +import { toPanelModelLibraryPanel } from '../../../library-panels/utils'; +import { DashboardModel, PanelModel } from '../../state'; export type PanelPluginInfo = { id: any; defaults: { gridPos: { w: any; h: any }; title: any } }; diff --git a/public/app/features/dashboard/components/AnnotationSettings/AngularEditorLoader.tsx b/public/app/features/dashboard/components/AnnotationSettings/AngularEditorLoader.tsx index 0ef7279c43c..bf04402ccbb 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AngularEditorLoader.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AngularEditorLoader.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { AnnotationQuery, DataSourceApi } from '@grafana/data'; import { AngularComponent, getAngularLoader } from '@grafana/runtime'; diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx index 01b9c873d30..9b78743f9d2 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx @@ -1,12 +1,15 @@ import React, { useState } from 'react'; -import { Checkbox, CollapsableSection, ColorValueEditor, Field, HorizontalGroup, Input } from '@grafana/ui'; -import { DashboardModel } from '../../state/DashboardModel'; -import { AnnotationQuery, DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data'; -import { DataSourcePicker, getDataSourceSrv } from '@grafana/runtime'; import { useAsync } from 'react-use'; -import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor'; -import { AngularEditorLoader } from './AngularEditorLoader'; + +import { AnnotationQuery, DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { DataSourcePicker, getDataSourceSrv } from '@grafana/runtime'; +import { Checkbox, CollapsableSection, ColorValueEditor, Field, HorizontalGroup, Input } from '@grafana/ui'; +import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor'; + +import { DashboardModel } from '../../state/DashboardModel'; + +import { AngularEditorLoader } from './AngularEditorLoader'; type Props = { editIdx: number; diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx index 60fd6d34614..0e59efeb395 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx @@ -1,9 +1,12 @@ import React, { useState } from 'react'; + +import { arrayUtils } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime'; import { DeleteButton, Icon, IconButton, VerticalGroup } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; + import { DashboardModel } from '../../state/DashboardModel'; import { ListNewButton } from '../DashboardSettings/ListNewButton'; -import { arrayUtils } from '@grafana/data'; type Props = { dashboard: DashboardModel; @@ -26,6 +29,7 @@ export const AnnotationSettingsList: React.FC = ({ dashboard, onNew, onEd const showEmptyListCTA = annotations.length === 0 || (annotations.length === 1 && annotations[0].builtIn); + const dataSourceSrv = getDataSourceSrv(); return ( {annotations.length > 0 && ( @@ -50,7 +54,7 @@ export const AnnotationSettingsList: React.FC = ({ dashboard, onNew, onEd )} onEdit(idx)}> - {annotation.datasource?.uid} + {dataSourceSrv.getInstanceSettings(annotation.datasource)?.name || annotation.datasource?.uid} {idx !== 0 && ( diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts index 83482884b08..038812d89c6 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts @@ -1,13 +1,16 @@ import { find } from 'lodash'; -import config from 'app/core/config'; -import { DashboardExporter, LibraryElementExport } from './DashboardExporter'; -import { DashboardModel } from '../../state/DashboardModel'; + import { DataSourceInstanceSettings, DataSourceRef, PanelPluginMeta } from '@grafana/data'; +import config from 'app/core/config'; + +import { LibraryElementKind } from '../../../library-panels/types'; import { variableAdapters } from '../../../variables/adapters'; import { createConstantVariableAdapter } from '../../../variables/constant/adapter'; -import { createQueryVariableAdapter } from '../../../variables/query/adapter'; import { createDataSourceVariableAdapter } from '../../../variables/datasource/adapter'; -import { LibraryElementKind } from '../../../library-panels/types'; +import { createQueryVariableAdapter } from '../../../variables/query/adapter'; +import { DashboardModel } from '../../state/DashboardModel'; + +import { DashboardExporter, LibraryElementExport } from './DashboardExporter'; jest.mock('app/core/store', () => { return { @@ -276,6 +279,10 @@ describe('given dashboard with repeated panels', () => { }, ], }, + { + id: 5, + targets: [{ scenarioId: 'random_walk', refId: 'A' }], + }, ], }; @@ -312,6 +319,23 @@ describe('given dashboard with repeated panels', () => { expect(panel.datasource.uid).toBe('${DS_GFDB}'); }); + it('should explicitly specify default datasources', () => { + const panel = exported.panels[7]; + expect(exported.__inputs.some((ds: Record) => ds.name === 'DS_GFDB')).toBeTruthy(); + expect(panel.datasource.uid).toBe('${DS_GFDB}'); + expect(panel.targets[0].datasource).toBe('${DS_GFDB}'); + }); + + it('should not include default datasource in __inputs unnecessarily', async () => { + const testJson: any = { + panels: [{ id: 1, datasource: { uid: 'other', type: 'other' }, type: 'graph' }], + }; + const testDash = new DashboardModel(testJson); + const exporter = new DashboardExporter(); + const exportedJson: any = await exporter.makeExportable(testDash); + expect(exportedJson.__inputs.some((ds: Record) => ds.name === 'DS_GFDB')).toBeFalsy(); + }); + it('should replace datasource refs in collapsed row', () => { const panel = exported.panels[6].panels[0]; expect(panel.datasource.uid).toBe('${DS_GFDB}'); @@ -453,7 +477,7 @@ stubs['other2'] = { meta: { id: 'other2', info: { version: '1.2.1' }, name: 'OtherDB_2' }, }; -stubs['-- Mixed --'] = { +stubs['mixed'] = { name: 'mixed', meta: { id: 'mixed', @@ -463,7 +487,7 @@ stubs['-- Mixed --'] = { }, }; -stubs['-- Grafana --'] = { +stubs['grafana'] = { name: '-- Grafana --', meta: { id: 'grafana', diff --git a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts index 3e7886e4c1c..6fea11bd39f 100644 --- a/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts +++ b/public/app/features/dashboard/components/DashExportModal/DashboardExporter.ts @@ -1,14 +1,15 @@ import { defaults, each, sortBy } from 'lodash'; -import config from 'app/core/config'; -import { DashboardModel } from '../../state/DashboardModel'; -import { PanelModel } from 'app/features/dashboard/state'; import { PanelPluginMeta } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; -import { VariableOption, VariableRefresh } from '../../../variables/types'; -import { isConstant, isQuery } from '../../../variables/guard'; -import { LibraryElementKind } from '../../../library-panels/types'; +import config from 'app/core/config'; +import { PanelModel } from 'app/features/dashboard/state'; + import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; +import { LibraryElementKind } from '../../../library-panels/types'; +import { isConstant, isQuery } from '../../../variables/guard'; +import { VariableOption, VariableRefresh } from '../../../variables/types'; +import { DashboardModel } from '../../state/DashboardModel'; interface Input { name: string; @@ -126,15 +127,18 @@ export class DashboardExporter { }; const processPanel = (panel: PanelModel) => { - if (panel.datasource !== undefined && panel.datasource !== null) { + const isRegularPanel = + (panel.repeatPanelId === undefined || panel.repeatPanelId === null) && + !('collapsed' in panel) && + !('panels' in panel); + + if (isRegularPanel) { templateizeDatasourceUsage(panel); } if (panel.targets) { for (const target of panel.targets) { - if (target.datasource !== undefined) { - templateizeDatasourceUsage(target); - } + templateizeDatasourceUsage(target); } } diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 084e52ed1b3..558f9dacd2a 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -1,26 +1,24 @@ -// Libaries import React, { FC, ReactNode } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useLocation } from 'react-router-dom'; -// Utils & Services + +import { locationUtil, textUtil } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar, useForceUpdate } from '@grafana/ui'; +import config from 'app/core/config'; +import { toggleKioskMode } from 'app/core/navigation/kiosk'; +import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal'; +import { SaveDashboardProxy } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardProxy'; +import { ShareModal } from 'app/features/dashboard/components/ShareModal'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; -// Components +import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; +import { KioskMode } from 'app/types'; + +import { getDashboardSrv } from '../../services/DashboardSrv'; +import { DashboardModel } from '../../state'; + import { DashNavButton } from './DashNavButton'; import { DashNavTimeControls } from './DashNavTimeControls'; -import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar, useForceUpdate } from '@grafana/ui'; -import { locationUtil, textUtil } from '@grafana/data'; -// State -import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; -// Types -import { DashboardModel } from '../../state'; -import { KioskMode } from 'app/types'; -import { ShareModal } from 'app/features/dashboard/components/ShareModal'; -import { SaveDashboardProxy } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardProxy'; -import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal'; -import { locationService } from '@grafana/runtime'; -import { toggleKioskMode } from 'app/core/navigation/kiosk'; -import { getDashboardSrv } from '../../services/DashboardSrv'; -import config from 'app/core/config'; const mapDispatchToProps = { updateTimeZoneForSession, diff --git a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx index 5f8e3e6a2d8..48db37402ca 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx @@ -1,9 +1,10 @@ // Libraries -import React, { FunctionComponent, MouseEvent } from 'react'; import { css } from '@emotion/css'; +import React, { FunctionComponent, MouseEvent } from 'react'; + // Components -import { IconName, IconType, IconSize, IconButton, useTheme, stylesFactory } from '@grafana/ui'; import { GrafanaTheme } from '@grafana/data'; +import { IconName, IconType, IconSize, IconButton, useTheme, stylesFactory } from '@grafana/ui'; interface Props { icon?: IconName; diff --git a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.test.tsx b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.test.tsx index 29593cac102..b855f9633bb 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.test.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import { DashNavTimeControls } from './DashNavTimeControls'; -import { DashboardModel } from '../../state/DashboardModel'; +import React from 'react'; + import { getDashboardModel } from '../../../../../test/helpers/getDashboardModel'; +import { DashboardModel } from '../../state/DashboardModel'; + +import { DashNavTimeControls } from './DashNavTimeControls'; describe('DashNavTimeControls', () => { let dashboardModel: DashboardModel; diff --git a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx index 640efa92c3a..d3f3c29a3cb 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx @@ -1,20 +1,15 @@ -// Libraries import React, { Component } from 'react'; +import { Unsubscribable } from 'rxjs'; + import { dateMath, TimeRange, TimeZone } from '@grafana/data'; import { TimeRangeUpdatedEvent } from '@grafana/runtime'; - -// Types -import { DashboardModel } from '../../state'; - -// Components import { defaultIntervals, RefreshPicker, ToolbarButtonRow } from '@grafana/ui'; import { TimePickerWithHistory } from 'app/core/components/TimePicker/TimePickerWithHistory'; - -// Utils & Services -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { appEvents } from 'app/core/core'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; + import { ShiftTimeEvent, ShiftTimeEventDirection, ZoomOutEvent } from '../../../../types/events'; -import { Unsubscribable } from 'rxjs'; +import { DashboardModel } from '../../state'; export interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DashboardComments/DashboardCommentsModal.tsx b/public/app/features/dashboard/components/DashboardComments/DashboardCommentsModal.tsx index 5f77608749c..fff48e1b531 100644 --- a/public/app/features/dashboard/components/DashboardComments/DashboardCommentsModal.tsx +++ b/public/app/features/dashboard/components/DashboardComments/DashboardCommentsModal.tsx @@ -1,9 +1,10 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { Modal, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; +import React from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Modal, useStyles2 } from '@grafana/ui'; import { CommentManager } from 'app/features/comments/CommentManager'; + import { DashboardModel } from '../../state/DashboardModel'; type Props = { diff --git a/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx b/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx index 4da79364423..ab6bb41f072 100644 --- a/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx +++ b/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx @@ -1,9 +1,10 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { Alert, useStyles } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { DashboardInitError, AppNotificationSeverity } from 'app/types'; +import { Alert, useStyles } from '@grafana/ui'; import { getMessageFromError } from 'app/core/utils/errors'; +import { DashboardInitError, AppNotificationSeverity } from 'app/types'; export interface Props { initError?: DashboardInitError; diff --git a/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx b/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx index 97ee5b5d417..0ab5f58f308 100644 --- a/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx +++ b/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx @@ -1,8 +1,9 @@ -import React from 'react'; import { css, keyframes } from '@emotion/css'; -import { Button, HorizontalGroup, Spinner, useStyles, VerticalGroup } from '@grafana/ui'; -import { locationService } from '@grafana/runtime'; +import React from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { Button, HorizontalGroup, Spinner, useStyles, VerticalGroup } from '@grafana/ui'; import { DashboardInitPhase } from 'app/types'; export interface Props { diff --git a/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx index b43fdf49642..989786dec44 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import { contextSrv } from 'app/core/core'; -import { DashboardModel } from '../../state'; -import { AccessControlAction } from 'app/types'; + import { Permissions } from 'app/core/components/AccessControl'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction } from 'app/types'; + +import { DashboardModel } from '../../state'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx index 9d9488f5758..b417791bbb4 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/DashboardPermissions.tsx @@ -1,20 +1,22 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { Tooltip, Icon, Button } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; +import AddPermission from 'app/core/components/PermissionList/AddPermission'; +import PermissionList from 'app/core/components/PermissionList/PermissionList'; +import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo'; import { StoreState } from 'app/types'; import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl'; + +import { checkFolderPermissions } from '../../../folders/state/actions'; +import { DashboardModel } from '../../state/DashboardModel'; import { getDashboardPermissions, addDashboardPermission, removeDashboardPermission, updateDashboardPermission, } from '../../state/actions'; -import { checkFolderPermissions } from '../../../folders/state/actions'; -import { DashboardModel } from '../../state/DashboardModel'; -import PermissionList from 'app/core/components/PermissionList/PermissionList'; -import AddPermission from 'app/core/components/PermissionList/AddPermission'; -import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo'; const mapStateToProps = (state: StoreState) => ({ permissions: state.dashboard.permissions, diff --git a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx index 6622d81152c..fc19f772d4f 100644 --- a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx +++ b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx @@ -1,6 +1,7 @@ +import { setContextSrv } from '../../../../core/services/context_srv'; import { DashboardModel } from '../../state/DashboardModel'; import { PanelModel } from '../../state/PanelModel'; -import { setContextSrv } from '../../../../core/services/context_srv'; + import { hasChanges, ignoreChanges } from './DashboardPrompt'; function getDefaultDashboardModel(): DashboardModel { diff --git a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx index b6823e765e3..f694e245afe 100644 --- a/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx +++ b/public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.tsx @@ -1,19 +1,21 @@ -import { locationService } from '@grafana/runtime'; -import { contextSrv } from 'app/core/services/context_srv'; -import React, { useContext, useEffect, useState } from 'react'; -import { Prompt } from 'react-router-dom'; -import { DashboardModel } from '../../state/DashboardModel'; -import { each, filter, find } from 'lodash'; -import { UnsavedChangesModal } from '../SaveDashboard/UnsavedChangesModal'; import * as H from 'history'; -import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal'; -import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types'; +import { each, filter, find } from 'lodash'; +import React, { useContext, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { discardPanelChanges, exitPanelEditor } from '../PanelEditor/state/actions'; +import { Prompt } from 'react-router-dom'; + +import { locationService } from '@grafana/runtime'; import { ModalsContext } from '@grafana/ui'; import { appEvents } from 'app/core/app_events'; +import { contextSrv } from 'app/core/services/context_srv'; +import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal'; +import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types'; import { DashboardSavedEvent } from 'app/types/events'; +import { DashboardModel } from '../../state/DashboardModel'; +import { discardPanelChanges, exitPanelEditor } from '../PanelEditor/state/actions'; +import { UnsavedChangesModal } from '../SaveDashboard/UnsavedChangesModal'; + export interface Props { dashboard: DashboardModel; } diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx index 9da5aa3b5b7..e35d6d1487e 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { DashboardRow } from './DashboardRow'; +import React from 'react'; + import { PanelModel } from '../../state/PanelModel'; +import { DashboardRow } from './DashboardRow'; + describe('DashboardRow', () => { let wrapper: any, panel: PanelModel, dashboardMock: any; diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx index 6848ba1b13e..c2fbc41dbd1 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx @@ -1,14 +1,16 @@ -import React from 'react'; import classNames from 'classnames'; -import { Icon } from '@grafana/ui'; -import { PanelModel } from '../../state/PanelModel'; -import { DashboardModel } from '../../state/DashboardModel'; -import appEvents from 'app/core/app_events'; -import { RowOptionsButton } from '../RowOptions/RowOptionsButton'; -import { getTemplateSrv, RefreshEvent } from '@grafana/runtime'; -import { ShowConfirmModalEvent } from '../../../../types/events'; +import React from 'react'; import { Unsubscribable } from 'rxjs'; + import { selectors } from '@grafana/e2e-selectors'; +import { getTemplateSrv, RefreshEvent } from '@grafana/runtime'; +import { Icon } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; + +import { ShowConfirmModalEvent } from '../../../../types/events'; +import { DashboardModel } from '../../state/DashboardModel'; +import { PanelModel } from '../../state/PanelModel'; +import { RowOptionsButton } from '../RowOptions/RowOptionsButton'; export interface DashboardRowProps { panel: PanelModel; diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx index 07c3d3d2b76..b838b8dd479 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx @@ -1,13 +1,14 @@ -import React from 'react'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; import { within } from '@testing-library/dom'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; import { setAngularLoader, setDataSourceSrv } from '@grafana/runtime'; -import { AnnotationsSettings } from './AnnotationsSettings'; import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; +import { AnnotationsSettings } from './AnnotationsSettings'; + describe('AnnotationsSettings', () => { let dashboard: any; @@ -15,7 +16,7 @@ describe('AnnotationsSettings', () => { grafana: mockDataSource( { name: 'Grafana', - uid: 'Grafana', + uid: 'uid1', type: 'grafana', isDefault: true, }, @@ -24,7 +25,7 @@ describe('AnnotationsSettings', () => { Testdata: mockDataSource( { name: 'Testdata', - uid: 'Testdata', + uid: 'uid2', type: 'testdata', isDefault: true, }, @@ -33,7 +34,7 @@ describe('AnnotationsSettings', () => { Prometheus: mockDataSource( { name: 'Prometheus', - uid: 'Prometheus', + uid: 'uid3', type: 'prometheus', }, { annotations: true } @@ -63,7 +64,7 @@ describe('AnnotationsSettings', () => { list: [ { builtIn: 1, - datasource: { uid: 'Grafana', type: 'grafana' }, + datasource: { uid: 'uid1', type: 'grafana' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', @@ -76,7 +77,7 @@ describe('AnnotationsSettings', () => { }; }); - test('it renders a header and cta if no annotations or only builtIn annotation', () => { + test('it renders a header and cta if no annotations or only builtIn annotation', async () => { render(); expect(screen.getByRole('heading', { name: /annotations/i })).toBeInTheDocument(); @@ -87,7 +88,7 @@ describe('AnnotationsSettings', () => { ).toBeInTheDocument(); expect(screen.queryByRole('link', { name: /annotations documentation/i })).toBeInTheDocument(); - userEvent.click(screen.getByRole('cell', { name: /annotations & alerts \(built\-in\)/i })); + await userEvent.click(screen.getByRole('cell', { name: /annotations & alerts \(built\-in\)/i })); const heading = screen.getByRole('heading', { name: /annotations edit/i, @@ -96,13 +97,13 @@ describe('AnnotationsSettings', () => { expect(heading).toBeInTheDocument(); - userEvent.clear(nameInput); - userEvent.type(nameInput, 'My Annotation'); + await userEvent.clear(nameInput); + await userEvent.type(nameInput, 'My Annotation'); expect(screen.queryByText(/grafana/i)).toBeInTheDocument(); expect(screen.getByRole('checkbox', { name: /hidden/i })).toBeChecked(); - userEvent.click(within(heading).getByText(/annotations/i)); + await userEvent.click(within(heading).getByText(/annotations/i)); expect(screen.getByRole('table')).toBeInTheDocument(); expect(screen.getByRole('row', { name: /my annotation \(built\-in\) grafana/i })).toBeInTheDocument(); @@ -111,8 +112,8 @@ describe('AnnotationsSettings', () => { ).toBeInTheDocument(); expect(screen.queryByRole('button', { name: /new query/i })).not.toBeInTheDocument(); - userEvent.click(screen.getAllByLabelText(/Delete query with title/)[0]); - userEvent.click(screen.getByRole('button', { name: 'Delete' })); + await userEvent.click(screen.getAllByLabelText(/Delete query with title/)[0]); + await userEvent.click(screen.getByRole('button', { name: 'Delete' })); expect(screen.queryAllByRole('row').length).toBe(0); expect( @@ -120,12 +121,12 @@ describe('AnnotationsSettings', () => { ).toBeInTheDocument(); }); - test('it renders a sortable table of annotations', () => { + test('it renders the anotation names or uid if annotation doesnt exist', async () => { const annotationsList = [ ...dashboard.annotations.list, { builtIn: 0, - datasource: { uid: 'Prometheus', type: 'prometheus' }, + datasource: { uid: 'uid3', type: 'prometheus' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', @@ -134,7 +135,41 @@ describe('AnnotationsSettings', () => { }, { builtIn: 0, - datasource: { uid: 'Prometheus', type: 'prometheus' }, + datasource: { uid: 'deletedAnnotationId', type: 'prometheus' }, + enable: true, + hide: true, + iconColor: 'rgba(0, 211, 255, 1)', + name: 'Annotation 2', + type: 'dashboard', + }, + ]; + const dashboardWithAnnotations = { + ...dashboard, + annotations: { + list: [...annotationsList], + }, + }; + render(); + // Check that we have the correct annotations + expect(screen.queryByText(/prometheus/i)).toBeInTheDocument(); + expect(screen.queryByText(/deletedAnnotationId/i)).toBeInTheDocument(); + }); + + test('it renders a sortable table of annotations', async () => { + const annotationsList = [ + ...dashboard.annotations.list, + { + builtIn: 0, + datasource: { uid: 'uid3', type: 'prometheus' }, + enable: true, + hide: true, + iconColor: 'rgba(0, 211, 255, 1)', + name: 'Annotation 2', + type: 'dashboard', + }, + { + builtIn: 0, + datasource: { uid: 'uid3', type: 'prometheus' }, enable: true, hide: true, iconColor: 'rgba(0, 211, 255, 1)', @@ -164,9 +199,9 @@ describe('AnnotationsSettings', () => { expect(within(getTableBodyRows()[1]).queryByText(/annotation 2/i)).toBeInTheDocument(); expect(within(getTableBodyRows()[2]).queryByText(/annotation 3/i)).toBeInTheDocument(); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[0]); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[1]); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-up' })[0]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[0]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[1]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-up' })[0]); // Checking if it has changed the sorting accordingly expect(within(getTableBodyRows()[0]).queryByText(/annotation 3/i)).toBeInTheDocument(); @@ -177,7 +212,7 @@ describe('AnnotationsSettings', () => { test('it renders a form for adding/editing annotations', async () => { render(); - userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))); + await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))); const heading = screen.getByRole('heading', { name: /annotations edit/i, @@ -186,19 +221,19 @@ describe('AnnotationsSettings', () => { expect(heading).toBeInTheDocument(); - userEvent.clear(nameInput); - userEvent.type(nameInput, 'My Prometheus Annotation'); + await userEvent.clear(nameInput); + await userEvent.type(nameInput, 'My Prometheus Annotation'); - userEvent.click(screen.getByText(/testdata/i)); + await userEvent.click(screen.getByText(/testdata/i)); expect(await screen.findByText(/Prometheus/i)).toBeVisible(); expect(screen.queryAllByText(/testdata/i)).toHaveLength(2); - userEvent.click(screen.getByText(/prometheus/i)); + await userEvent.click(screen.getByText(/prometheus/i)); expect(screen.getByRole('checkbox', { name: /hidden/i })).not.toBeChecked(); - userEvent.click(within(heading).getByText(/annotations/i)); + await userEvent.click(within(heading).getByText(/annotations/i)); expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(2); expect(screen.queryByRole('row', { name: /my prometheus annotation prometheus/i })).toBeInTheDocument(); @@ -207,14 +242,14 @@ describe('AnnotationsSettings', () => { screen.queryByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query')) ).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: /new query/i })); + await userEvent.click(screen.getByRole('button', { name: /new query/i })); - userEvent.click(within(screen.getByRole('heading', { name: /annotations edit/i })).getByText(/annotations/i)); + await userEvent.click(within(screen.getByRole('heading', { name: /annotations edit/i })).getByText(/annotations/i)); expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(3); - userEvent.click(screen.getAllByLabelText(/Delete query with title/)[0]); - userEvent.click(screen.getByRole('button', { name: 'Delete' })); + await userEvent.click(screen.getAllByLabelText(/Delete query with title/)[0]); + await userEvent.click(screen.getByRole('button', { name: 'Delete' })); expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(2); }); diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx index 371b6c85422..0ce57e741af 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx @@ -1,8 +1,11 @@ +import React, { useState } from 'react'; + import { AnnotationQuery, getDataSourceRef } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; -import React, { useState } from 'react'; + import { DashboardModel } from '../../state/DashboardModel'; import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } from '../AnnotationSettings'; + import { DashboardSettingsHeader } from './DashboardSettingsHeader'; interface Props { diff --git a/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.test.tsx b/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.test.tsx index 948dfd93c15..5f6ffb16a33 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { defaultIntervals } from '@grafana/ui'; -import { AutoRefreshIntervals, getValidIntervals, Props, validateIntervals } from './AutoRefreshIntervals'; import { TimeSrv } from '../../services/TimeSrv'; +import { AutoRefreshIntervals, getValidIntervals, Props, validateIntervals } from './AutoRefreshIntervals'; + const setupTestContext = (options: Partial) => { const defaults: Props = { refreshIntervals: ['1s', '5s', '10s'], @@ -49,11 +51,11 @@ describe('AutoRefreshIntervals', () => { }); describe('when input loses focus and intervals are valid', () => { - it('then onRefreshIntervalChange should be called', () => { + it('then onRefreshIntervalChange should be called', async () => { const { props } = setupTestContext({ validateIntervalsFunc: () => null }); - userEvent.type(screen.getByRole('textbox'), ',30s'); - userEvent.tab(); + await userEvent.type(screen.getByRole('textbox'), ',30s'); + await userEvent.tab(); expect(screen.getByRole('textbox')).toHaveValue('1s,5s,10s,30s'); expect(props.onRefreshIntervalChange).toHaveBeenCalledTimes(1); @@ -62,11 +64,11 @@ describe('AutoRefreshIntervals', () => { }); describe('when input loses focus and intervals are invalid', () => { - it('then onRefreshIntervalChange should not be called', () => { + it('then onRefreshIntervalChange should not be called', async () => { const { props } = setupTestContext({ validateIntervalsFunc: () => 'Not valid' }); - userEvent.type(screen.getByRole('textbox'), ',30q'); - userEvent.tab(); + await userEvent.type(screen.getByRole('textbox'), ',30q'); + await userEvent.tab(); expect(screen.getByRole('textbox')).toHaveValue('1s,5s,10s,30q'); expect(props.onRefreshIntervalChange).toHaveBeenCalledTimes(0); @@ -74,14 +76,14 @@ describe('AutoRefreshIntervals', () => { }); describe('when input loses focus and previous intervals were invalid', () => { - it('then onRefreshIntervalChange should be called', () => { + it('then onRefreshIntervalChange should be called', async () => { const validateIntervalsFunc = jest.fn().mockReturnValueOnce('Not valid').mockReturnValue(null); const { props } = setupTestContext({ validateIntervalsFunc }); - userEvent.type(screen.getByRole('textbox'), ',30q'); - userEvent.tab(); - userEvent.type(screen.getByRole('textbox'), '{backspace}s'); - userEvent.tab(); + await userEvent.type(screen.getByRole('textbox'), ',30q'); + await userEvent.tab(); + await userEvent.type(screen.getByRole('textbox'), '{backspace}s'); + await userEvent.tab(); expect(screen.getByRole('textbox')).toHaveValue('1s,5s,10s,30s'); expect(props.onRefreshIntervalChange).toHaveBeenCalledTimes(1); diff --git a/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx b/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx index 5554971c657..fa9ec12888b 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AutoRefreshIntervals.tsx @@ -1,4 +1,5 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; + import { Input, defaultIntervals, Field } from '@grafana/ui'; import { getTimeSrv } from '../../services/TimeSrv'; diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx index 1a0acb63656..886716a6af1 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx @@ -1,25 +1,28 @@ +import { css, cx } from '@emotion/css'; +import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; +import { useOverlay } from '@react-aria/overlays'; import React, { useCallback, useMemo, useRef } from 'react'; import { Link } from 'react-router-dom'; -import { FocusScope } from '@react-aria/focus'; -import { useDialog } from '@react-aria/dialog'; -import { useOverlay } from '@react-aria/overlays'; -import { css, cx } from '@emotion/css'; + +import { GrafanaTheme2, locationUtil } from '@grafana/data'; +import { locationService, reportInteraction } from '@grafana/runtime'; import { Button, CustomScrollbar, Icon, IconName, PageToolbar, stylesFactory, useForceUpdate } from '@grafana/ui'; import config from 'app/core/config'; import { contextSrv } from 'app/core/services/context_srv'; -import { DashboardModel } from '../../state/DashboardModel'; -import { SaveDashboardAsButton, SaveDashboardButton } from '../SaveDashboard/SaveDashboardButton'; +import { AccessControlAction } from 'app/types'; + import { VariableEditorContainer } from '../../../variables/editor/VariableEditorContainer'; -import { DashboardPermissions } from '../DashboardPermissions/DashboardPermissions'; +import { DashboardModel } from '../../state/DashboardModel'; import { AccessControlDashboardPermissions } from '../DashboardPermissions/AccessControlDashboardPermissions'; -import { GeneralSettings } from './GeneralSettings'; +import { DashboardPermissions } from '../DashboardPermissions/DashboardPermissions'; +import { SaveDashboardAsButton, SaveDashboardButton } from '../SaveDashboard/SaveDashboardButton'; + import { AnnotationsSettings } from './AnnotationsSettings'; +import { GeneralSettings } from './GeneralSettings'; +import { JsonEditorSettings } from './JsonEditorSettings'; import { LinksSettings } from './LinksSettings'; import { VersionsSettings } from './VersionsSettings'; -import { JsonEditorSettings } from './JsonEditorSettings'; -import { GrafanaTheme2, locationUtil } from '@grafana/data'; -import { locationService, reportInteraction } from '@grafana/runtime'; -import { AccessControlAction } from 'app/types'; export interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettingsHeader.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettingsHeader.tsx index e17ee1daa3c..4e1485f9239 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettingsHeader.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettingsHeader.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Icon, HorizontalGroup } from '@grafana/ui'; type Props = { diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx index dbec19a2961..c7f17b17dc0 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { byRole } from 'testing-library-selector'; + +import { selectors } from '@grafana/e2e-selectors'; import { selectOptionInTest } from '@grafana/ui'; -import { byRole } from 'testing-library-selector'; -import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings'; import { DashboardModel } from '../../state'; -import { selectors } from '@grafana/e2e-selectors'; + +import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings'; const setupTestContext = (options: Partial) => { const defaults: Props = { @@ -48,9 +50,9 @@ describe('General Settings', () => { describe('when timezone is changed', () => { it('should call update function', async () => { const { props } = setupTestContext({}); - userEvent.click(screen.getByTestId(selectors.components.TimeZonePicker.containerV2)); + await userEvent.click(screen.getByTestId(selectors.components.TimeZonePicker.containerV2)); const timeZonePicker = screen.getByTestId(selectors.components.TimeZonePicker.containerV2); - userEvent.click(byRole('combobox').get(timeZonePicker)); + await userEvent.click(byRole('combobox').get(timeZonePicker)); await selectOptionInTest(timeZonePicker, 'Browser Time'); expect(props.updateTimeZone).toHaveBeenCalledWith('browser'); expect(props.dashboard.timezone).toBe('browser'); diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index 9b2d3e77c8f..1ddfc1a4cef 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -1,16 +1,18 @@ import React, { useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { TimeZone } from '@grafana/data'; -import { CollapsableSection, Field, Input, RadioButtonGroup, TagsInput } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; +import { config } from '@grafana/runtime'; +import { CollapsableSection, Field, Input, RadioButtonGroup, TagsInput } from '@grafana/ui'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; +import { updateTimeZoneDashboard, updateWeekStartDashboard } from 'app/features/dashboard/state/actions'; + import { DashboardModel } from '../../state/DashboardModel'; import { DeleteDashboardButton } from '../DeleteDashboard/DeleteDashboardButton'; -import { TimePickerSettings } from './TimePickerSettings'; -import { updateTimeZoneDashboard, updateWeekStartDashboard } from 'app/features/dashboard/state/actions'; import { PreviewSettings } from './PreviewSettings'; -import { config } from '@grafana/runtime'; +import { TimePickerSettings } from './TimePickerSettings'; interface OwnProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx index e5da865a634..55e9317942e 100644 --- a/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/JsonEditorSettings.tsx @@ -1,11 +1,13 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, CodeEditor, HorizontalGroup, useStyles2 } from '@grafana/ui'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; + import { getDashboardSrv } from '../../services/DashboardSrv'; import { DashboardModel } from '../../state/DashboardModel'; -import { GrafanaTheme2 } from '@grafana/data'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx index a2646974940..f0156b67067 100644 --- a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; import { within } from '@testing-library/dom'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; import { LinksSettings } from './LinksSettings'; @@ -84,7 +84,7 @@ describe('LinksSettings', () => { ).not.toBeInTheDocument(); }); - test('it rearranges the order of dashboard links', () => { + test('it rearranges the order of dashboard links', async () => { // @ts-ignore render(); @@ -103,9 +103,9 @@ describe('LinksSettings', () => { assertRowHasText(1, links[1].title); assertRowHasText(2, links[2].url); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[0]); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[1]); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-up' })[0]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[0]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-down' })[1]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: 'arrow-up' })[0]); // Checking if it has changed the sorting accordingly assertRowHasText(0, links[2].url); @@ -113,35 +113,35 @@ describe('LinksSettings', () => { assertRowHasText(2, links[0].title); }); - test('it duplicates dashboard links', () => { + test('it duplicates dashboard links', async () => { // @ts-ignore render(); expect(getTableBodyRows().length).toBe(links.length); - userEvent.click(within(getTableBody()).getAllByRole('button', { name: /copy/i })[0]); + await userEvent.click(within(getTableBody()).getAllByRole('button', { name: /copy/i })[0]); expect(getTableBodyRows().length).toBe(links.length + 1); expect(within(getTableBody()).getAllByText(links[0].title).length).toBe(2); }); - test('it deletes dashboard links', () => { + test('it deletes dashboard links', async () => { // @ts-ignore render(); expect(getTableBodyRows().length).toBe(links.length); - userEvent.click(within(getTableBody()).getAllByLabelText(/Delete link with title/)[0]); - userEvent.click(within(getTableBody()).getByRole('button', { name: 'Delete' })); + await userEvent.click(within(getTableBody()).getAllByLabelText(/Delete link with title/)[0]); + await userEvent.click(within(getTableBody()).getByRole('button', { name: 'Delete' })); expect(getTableBodyRows().length).toBe(links.length - 1); expect(within(getTableBody()).queryByText(links[0].title)).not.toBeInTheDocument(); }); - test('it renders a form which modifies dashboard links', () => { + test('it renders a form which modifies dashboard links', async () => { // @ts-ignore render(); - userEvent.click(screen.getByRole('button', { name: /new/i })); + await userEvent.click(screen.getByRole('button', { name: /new/i })); expect(screen.queryByText('Type')).toBeInTheDocument(); expect(screen.queryByText('Title')).toBeInTheDocument(); @@ -151,29 +151,29 @@ describe('LinksSettings', () => { expect(screen.queryByText('Tooltip')).not.toBeInTheDocument(); expect(screen.queryByText('Icon')).not.toBeInTheDocument(); - userEvent.click(screen.getByText('Dashboards')); + await userEvent.click(screen.getByText('Dashboards')); expect(screen.queryAllByText('Dashboards')).toHaveLength(2); expect(screen.queryByText('Link')).toBeVisible(); - userEvent.click(screen.getByText('Link')); + await userEvent.click(screen.getByText('Link')); expect(screen.queryByText('URL')).toBeInTheDocument(); expect(screen.queryByText('Tooltip')).toBeInTheDocument(); expect(screen.queryByText('Icon')).toBeInTheDocument(); - userEvent.clear(screen.getByRole('textbox', { name: /title/i })); - userEvent.type(screen.getByRole('textbox', { name: /title/i }), 'New Dashboard Link'); - userEvent.click( + await userEvent.clear(screen.getByRole('textbox', { name: /title/i })); + await userEvent.type(screen.getByRole('textbox', { name: /title/i }), 'New Dashboard Link'); + await userEvent.click( within(screen.getByRole('heading', { name: /dashboard links edit/i })).getByText(/dashboard links/i) ); expect(getTableBodyRows().length).toBe(links.length + 1); expect(within(getTableBody()).queryByText('New Dashboard Link')).toBeInTheDocument(); - userEvent.click(screen.getAllByText(links[0].type)[0]); - userEvent.clear(screen.getByRole('textbox', { name: /title/i })); - userEvent.type(screen.getByRole('textbox', { name: /title/i }), 'The first dashboard link'); - userEvent.click( + await userEvent.click(screen.getAllByText(links[0].type)[0]); + await userEvent.clear(screen.getByRole('textbox', { name: /title/i })); + await userEvent.type(screen.getByRole('textbox', { name: /title/i }), 'The first dashboard link'); + await userEvent.click( within(screen.getByRole('heading', { name: /dashboard links edit/i })).getByText(/dashboard links/i) ); diff --git a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx index 4b3eebac82e..b000a62e4ae 100644 --- a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; + import { DashboardModel } from '../../state/DashboardModel'; import { LinkSettingsEdit, LinkSettingsList } from '../LinksSettings'; import { newLink } from '../LinksSettings/LinkSettingsEdit'; + import { DashboardSettingsHeader } from './DashboardSettingsHeader'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx b/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx index be40e1ddb7f..2f16558d0b0 100644 --- a/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx @@ -1,7 +1,8 @@ -import React, { ButtonHTMLAttributes } from 'react'; -import { Button, useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { ButtonHTMLAttributes } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { Button, useStyles } from '@grafana/ui'; export interface Props extends ButtonHTMLAttributes {} diff --git a/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx index 0e7202b0a74..407c2f96783 100644 --- a/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/PreviewSettings.tsx @@ -1,7 +1,8 @@ import React, { PureComponent } from 'react'; + import { Button, CollapsableSection, FileUpload } from '@grafana/ui'; -import { getThumbnailURL } from 'app/features/search/components/SearchCard'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { getThumbnailURL } from 'app/features/search/components/SearchCard'; interface Props { uid: string; diff --git a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx index d4374ca35eb..e52c92cd2e3 100644 --- a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx @@ -1,8 +1,10 @@ -import React, { PureComponent } from 'react'; -import { CollapsableSection, Field, Input, Switch, TimeZonePicker, WeekStartPicker } from '@grafana/ui'; -import { rangeUtil, TimeZone } from '@grafana/data'; import { isEmpty } from 'lodash'; +import React, { PureComponent } from 'react'; + +import { rangeUtil, TimeZone } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { CollapsableSection, Field, Input, Switch, TimeZonePicker, WeekStartPicker } from '@grafana/ui'; + import { AutoRefreshIntervals } from './AutoRefreshIntervals'; interface Props { diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx index 00788bbe65d..75baf12aa16 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import '@testing-library/jest-dom'; -import { render, screen, waitFor } from '@testing-library/react'; import { within } from '@testing-library/dom'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/dist/types/setup'; +import React from 'react'; + import { historySrv } from '../VersionHistory/HistorySrv'; + import { VersionsSettings, VERSIONS_FETCH_LIMIT } from './VersionsSettings'; import { versions, diffs } from './__mocks__/versions'; @@ -28,8 +30,18 @@ describe('VersionSettings', () => { getRelativeTime: jest.fn(() => 'time ago'), }; + let user: UserEvent; + beforeEach(() => { + // Need to use delay: null here to work with fakeTimers + // see https://github.com/testing-library/user-event/issues/833 + user = userEvent.setup({ delay: null }); jest.resetAllMocks(); + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); }); test('renders a header and a loading indicator followed by results in a table', async () => { @@ -102,7 +114,9 @@ describe('VersionSettings', () => { historySrv.getHistoryList // @ts-ignore .mockImplementationOnce(() => Promise.resolve(versions.slice(0, VERSIONS_FETCH_LIMIT))) - .mockImplementationOnce(() => Promise.resolve(versions.slice(VERSIONS_FETCH_LIMIT, versions.length))); + .mockImplementationOnce( + () => new Promise((resolve) => setTimeout(() => resolve(versions.slice(VERSIONS_FETCH_LIMIT)), 1000)) + ); render(); @@ -113,14 +127,16 @@ describe('VersionSettings', () => { expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(VERSIONS_FETCH_LIMIT); const showMoreButton = screen.getByRole('button', { name: /show more versions/i }); - userEvent.click(showMoreButton); + await user.click(showMoreButton); expect(historySrv.getHistoryList).toBeCalledTimes(2); - expect(screen.queryByText(/Fetching more entries/i)).toBeInTheDocument(); + expect(screen.getByText(/Fetching more entries/i)).toBeInTheDocument(); + jest.advanceTimersByTime(1000); - await waitFor(() => - expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(versions.length) - ); + await waitFor(() => { + expect(screen.queryByText(/Fetching more entries/i)).not.toBeInTheDocument(); + expect(within(screen.getAllByRole('rowgroup')[1]).getAllByRole('row').length).toBe(versions.length); + }); }); test('selecting two versions and clicking compare button should render compare view', async () => { @@ -139,17 +155,17 @@ describe('VersionSettings', () => { const compareButton = screen.getByRole('button', { name: /compare versions/i }); const tableBody = screen.getAllByRole('rowgroup')[1]; - userEvent.click(within(tableBody).getAllByRole('checkbox')[0]); - userEvent.click(within(tableBody).getAllByRole('checkbox')[VERSIONS_FETCH_LIMIT - 1]); + await user.click(within(tableBody).getAllByRole('checkbox')[0]); + await user.click(within(tableBody).getAllByRole('checkbox')[VERSIONS_FETCH_LIMIT - 1]); expect(compareButton).toBeEnabled(); - userEvent.click(within(tableBody).getAllByRole('checkbox')[1]); + await user.click(within(tableBody).getAllByRole('checkbox')[1]); expect(compareButton).toBeDisabled(); - userEvent.click(within(tableBody).getAllByRole('checkbox')[1]); - userEvent.click(compareButton); + await user.click(within(tableBody).getAllByRole('checkbox')[1]); + await user.click(compareButton); await waitFor(() => expect(screen.getByRole('heading', { name: /versions comparing 2 11/i })).toBeInTheDocument()); @@ -170,7 +186,7 @@ describe('VersionSettings', () => { expect(queryByFullText('version changed')).toBeInTheDocument(); expect(screen.queryByText(/view json diff/i)).toBeInTheDocument(); - userEvent.click(screen.getByText(/view json diff/i)); + await user.click(screen.getByText(/view json diff/i)); await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument()); }); diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx index f5702c2eb5a..448683461b9 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx @@ -1,5 +1,7 @@ import React, { PureComponent } from 'react'; + import { Spinner, HorizontalGroup } from '@grafana/ui'; + import { DashboardModel } from '../../state/DashboardModel'; import { historySrv, diff --git a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton.tsx b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton.tsx index a32207fc831..8b8ec48e753 100644 --- a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton.tsx +++ b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import { DeleteDashboardModal } from './DeleteDashboardModal'; + import { Button, ModalsController } from '@grafana/ui'; + import { DashboardModel } from '../../state'; +import { DeleteDashboardModal } from './DeleteDashboardModal'; + type Props = { dashboard: DashboardModel; }; diff --git a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx index 97ea543fb3e..11a62a02e3d 100644 --- a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx +++ b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { css } from '@emotion/css'; import { sumBy } from 'lodash'; -import { Modal, ConfirmModal, Button } from '@grafana/ui'; -import { DashboardModel, PanelModel } from '../../state'; -import { useDashboardDelete } from './useDashboardDelete'; +import React from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; + +import { Modal, ConfirmModal, Button } from '@grafana/ui'; import { config } from 'app/core/config'; +import { DashboardModel, PanelModel } from '../../state'; + +import { useDashboardDelete } from './useDashboardDelete'; + type DeleteDashboardModalProps = { hideModal(): void; dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/DeleteDashboard/useDashboardDelete.tsx b/public/app/features/dashboard/components/DeleteDashboard/useDashboardDelete.tsx index 263c45f7c1e..ed9429909c5 100644 --- a/public/app/features/dashboard/components/DeleteDashboard/useDashboardDelete.tsx +++ b/public/app/features/dashboard/components/DeleteDashboard/useDashboardDelete.tsx @@ -1,8 +1,9 @@ import { useEffect } from 'react'; import { useAsyncFn } from 'react-use'; + +import { locationService } from '@grafana/runtime'; import { useAppNotification } from 'app/core/copy/appNotification'; import { deleteDashboard } from 'app/features/manage-dashboards/state/actions'; -import { locationService } from '@grafana/runtime'; export const useDashboardDelete = (uid: string) => { const [state, onDeleteDashboard] = useAsyncFn(() => deleteDashboard(uid, false), []); diff --git a/public/app/features/dashboard/components/Inspector/InspectContent.tsx b/public/app/features/dashboard/components/Inspector/InspectContent.tsx index b9852f42821..daa3608548d 100644 --- a/public/app/features/dashboard/components/Inspector/InspectContent.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectContent.tsx @@ -1,16 +1,18 @@ import React, { useState } from 'react'; + import { DataSourceApi, formattedValueToString, getValueFormat, PanelData, PanelPlugin } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { Drawer, Tab, TabsBar } from '@grafana/ui'; -import { InspectMetadataTab } from 'app/features/inspector/InspectMetadataTab'; -import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab'; -import { QueryInspector } from 'app/features/inspector/QueryInspector'; -import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab'; -import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab'; import { InspectDataTab } from 'app/features/inspector/InspectDataTab'; +import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab'; +import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab'; +import { InspectMetadataTab } from 'app/features/inspector/InspectMetadataTab'; +import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab'; +import { QueryInspector } from 'app/features/inspector/QueryInspector'; import { InspectTab } from 'app/features/inspector/types'; -import { DashboardModel, PanelModel } from '../../state'; + import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; +import { DashboardModel, PanelModel } from '../../state'; interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/Inspector/PanelInspectActions.tsx b/public/app/features/dashboard/components/Inspector/PanelInspectActions.tsx index 795f7a897d0..e66449ae3f4 100644 --- a/public/app/features/dashboard/components/Inspector/PanelInspectActions.tsx +++ b/public/app/features/dashboard/components/Inspector/PanelInspectActions.tsx @@ -1,3 +1,7 @@ +import React, { ComponentType } from 'react'; + +import { PanelData } from '@grafana/data'; + import { PanelModel } from '../../state'; export interface PanelInspectActionProps { @@ -51,9 +55,6 @@ export interface PanelInspectActionSupplier { // NOTE: This is an internal/experimental API/hack and will change! // (window as any).grafanaPanelInspectActionSupplier = dummySupplier; -import React, { ComponentType } from 'react'; -import { PanelData } from '@grafana/data'; - interface InspectActionsTabProps { panel: PanelModel; data?: PanelData; diff --git a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx index 568149226a1..f0b9cb05111 100644 --- a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx +++ b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx @@ -1,16 +1,19 @@ import React, { useState } from 'react'; import { connect, MapStateToProps } from 'react-redux'; -import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; +import { useLocation } from 'react-router-dom'; + import { PanelPlugin } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { StoreState } from 'app/types'; -import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; -import { usePanelLatestData } from '../PanelEditor/usePanelLatestData'; -import { InspectContent } from './InspectContent'; -import { useDatasourceMetadata, useInspectTabs } from './hooks'; -import { useLocation } from 'react-router-dom'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { InspectTab } from 'app/features/inspector/types'; import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { StoreState } from 'app/types'; + +import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; +import { usePanelLatestData } from '../PanelEditor/usePanelLatestData'; + +import { InspectContent } from './InspectContent'; +import { useDatasourceMetadata, useInspectTabs } from './hooks'; interface OwnProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/Inspector/hooks.ts b/public/app/features/dashboard/components/Inspector/hooks.ts index c46fd8624a4..ca38e9ebcee 100644 --- a/public/app/features/dashboard/components/Inspector/hooks.ts +++ b/public/app/features/dashboard/components/Inspector/hooks.ts @@ -1,10 +1,13 @@ -import { DataQueryError, DataSourceApi, PanelData, PanelPlugin } from '@grafana/data'; +import { useMemo } from 'react'; import useAsync from 'react-use/lib/useAsync'; + +import { DataQueryError, DataSourceApi, PanelData, PanelPlugin } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; -import { useMemo } from 'react'; -import { supportsDataQuery } from '../PanelEditor/utils'; import { InspectTab } from 'app/features/inspector/types'; + +import { supportsDataQuery } from '../PanelEditor/utils'; + import { PanelInspectActionSupplier } from './PanelInspectActions'; /** diff --git a/public/app/features/dashboard/components/LinksSettings/LinkSettingsEdit.tsx b/public/app/features/dashboard/components/LinksSettings/LinkSettingsEdit.tsx index ac99dca1faf..cd5b2d16236 100644 --- a/public/app/features/dashboard/components/LinksSettings/LinkSettingsEdit.tsx +++ b/public/app/features/dashboard/components/LinksSettings/LinkSettingsEdit.tsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; -import { CollapsableSection, TagsInput, Select, Field, Input, Checkbox } from '@grafana/ui'; + import { SelectableValue } from '@grafana/data'; +import { CollapsableSection, TagsInput, Select, Field, Input, Checkbox } from '@grafana/ui'; + import { DashboardLink, DashboardModel } from '../../state/DashboardModel'; export const newLink = { diff --git a/public/app/features/dashboard/components/LinksSettings/LinkSettingsList.tsx b/public/app/features/dashboard/components/LinksSettings/LinkSettingsList.tsx index f23cedc728b..4db46c6143f 100644 --- a/public/app/features/dashboard/components/LinksSettings/LinkSettingsList.tsx +++ b/public/app/features/dashboard/components/LinksSettings/LinkSettingsList.tsx @@ -1,9 +1,11 @@ import React, { useState } from 'react'; + +import { arrayUtils } from '@grafana/data'; import { DeleteButton, HorizontalGroup, Icon, IconButton, TagList } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; + import { DashboardModel, DashboardLink } from '../../state/DashboardModel'; import { ListNewButton } from '../DashboardSettings/ListNewButton'; -import { arrayUtils } from '@grafana/data'; type LinkSettingsListProps = { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx b/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx index 605b9573856..b625700c025 100644 --- a/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/AngularPanelOptions.tsx @@ -1,18 +1,16 @@ -// Libraries import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -// Utils and services -import { AngularComponent, getAngularLoader } from '@grafana/runtime'; - -// Types -import { PanelModel, DashboardModel } from '../../state'; import { PanelPlugin, PanelPluginMeta } from '@grafana/data'; -import { changePanelPlugin } from 'app/features/panel/state/actions'; -import { StoreState } from 'app/types'; -import { getSectionOpenState, saveSectionOpenState } from './state/utils'; +import { AngularComponent, getAngularLoader } from '@grafana/runtime'; import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; +import { changePanelPlugin } from 'app/features/panel/state/actions'; import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { StoreState } from 'app/types'; + +import { PanelModel, DashboardModel } from '../../state'; + +import { getSectionOpenState, saveSectionOpenState } from './state/utils'; interface OwnProps { panel: PanelModel; diff --git a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx index 1af676e798a..f23f470a786 100644 --- a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx @@ -1,3 +1,8 @@ +import { css, cx } from '@emotion/css'; +import React from 'react'; +// @ts-ignore +import Highlighter from 'react-highlight-words'; + import { DynamicConfigValue, FieldConfigOptionsRegistry, @@ -5,11 +10,9 @@ import { FieldOverrideContext, GrafanaTheme, } from '@grafana/data'; -import React from 'react'; import { Counter, Field, HorizontalGroup, IconButton, Label, stylesFactory, useTheme } from '@grafana/ui'; -import { css, cx } from '@emotion/css'; + import { OptionsPaneCategory } from './OptionsPaneCategory'; -import Highlighter from 'react-highlight-words'; interface DynamicConfigValueEditorProps { property: DynamicConfigValue; diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx index 73e73e8ee24..2a025affce4 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx @@ -1,15 +1,17 @@ -import React from 'react'; -import { GrafanaTheme } from '@grafana/data'; -import { useStyles } from '@grafana/ui'; import { css } from '@emotion/css'; -import { selectors } from '@grafana/e2e-selectors'; -import { VisualizationButton } from './VisualizationButton'; -import { OptionsPaneOptions } from './OptionsPaneOptions'; +import React from 'react'; import { useSelector } from 'react-redux'; + +import { GrafanaTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { useStyles } from '@grafana/ui'; import { StoreState } from 'app/types'; + +import { OptionsPaneOptions } from './OptionsPaneOptions'; +import { VisualizationButton } from './VisualizationButton'; import { VisualizationSelectPane } from './VisualizationSelectPane'; -import { usePanelLatestData } from './usePanelLatestData'; import { OptionPaneRenderProps } from './types'; +import { usePanelLatestData } from './usePanelLatestData'; export const OptionsPane: React.FC = ({ plugin, diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx index 4c57d945ef4..8cfa6969013 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx @@ -1,13 +1,15 @@ -import React, { FC, ReactNode, useCallback, useEffect, useState, useRef } from 'react'; import { css, cx } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Counter, useStyles2 } from '@grafana/ui'; -import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers'; +import React, { FC, ReactNode, useCallback, useEffect, useState, useRef } from 'react'; import { useLocalStorage } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { Counter, useStyles2 } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { CollapseToggle } from 'app/features/alerting/unified/components/CollapseToggle'; +import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers'; + export interface OptionsPaneCategoryProps { id: string; title?: string; diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx index 5715aec6c47..ef887c9256e 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { OptionsPaneCategory } from './OptionsPaneCategory'; import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx index a8f2ef53233..97906d5d2bb 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx @@ -1,7 +1,10 @@ +import React, { ReactNode } from 'react'; +// @ts-ignore +import Highlighter from 'react-highlight-words'; + import { selectors } from '@grafana/e2e-selectors'; import { Field, Label } from '@grafana/ui'; -import React, { ReactNode } from 'react'; -import Highlighter from 'react-highlight-words'; + import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; import { OptionsPaneItemOverrides } from './OptionsPaneItemOverrides'; import { OptionPaneItemOverrideInfo } from './types'; diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemOverrides.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemOverrides.tsx index 7e086908788..769029e32f0 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemOverrides.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemOverrides.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { Tooltip, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, CSSObject } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Tooltip, useStyles2 } from '@grafana/ui'; + import { OptionPaneItemOverrideInfo } from './types'; export interface Props { diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx index f7f3e910936..c539c11acc3 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx @@ -1,5 +1,8 @@ -import React from 'react'; import { fireEvent, render, screen, within } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; + import { FieldConfigSource, FieldType, @@ -9,15 +12,14 @@ import { standardFieldConfigEditorRegistry, toDataFrame, } from '@grafana/data'; - import { selectors } from '@grafana/e2e-selectors'; -import { OptionsPaneOptions } from './OptionsPaneOptions'; -import { DashboardModel, PanelModel } from '../../state'; -import { Provider } from 'react-redux'; -import configureMockStore from 'redux-mock-store'; -import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; -import { dataOverrideTooltipDescription, overrideRuleTooltipDescription } from './state/getOptionOverrides'; import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/editors/registry'; +import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; + +import { DashboardModel, PanelModel } from '../../state'; + +import { OptionsPaneOptions } from './OptionsPaneOptions'; +import { dataOverrideTooltipDescription, overrideRuleTooltipDescription } from './state/getOptionOverrides'; standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs); diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx index 203c2095fc2..ac0bb0579fe 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx @@ -1,17 +1,20 @@ +import { css } from '@emotion/css'; import React, { useMemo, useState } from 'react'; + import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { CustomScrollbar, FilterInput, RadioButtonGroup, useStyles2 } from '@grafana/ui'; + +import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; + +import { AngularPanelOptions } from './AngularPanelOptions'; +import { OptionsPaneCategory } from './OptionsPaneCategory'; +import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; +import { getFieldOverrideCategories } from './getFieldOverrideElements'; +import { getLibraryPanelOptionsCategory } from './getLibraryPanelOptions'; import { getPanelFrameCategory } from './getPanelFrameOptions'; import { getVisualizationOptions } from './getVisualizationOptions'; -import { css } from '@emotion/css'; -import { OptionsPaneCategory } from './OptionsPaneCategory'; -import { getFieldOverrideCategories } from './getFieldOverrideElements'; -import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; import { OptionSearchEngine } from './state/OptionSearchEngine'; -import { AngularPanelOptions } from './AngularPanelOptions'; import { getRecentOptions } from './state/getRecentOptions'; -import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; -import { getLibraryPanelOptionsCategory } from './getLibraryPanelOptions'; import { OptionPaneRenderProps } from './types'; export const OptionsPaneOptions: React.FC = (props) => { diff --git a/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx b/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx index 3fef9023966..961868cedf6 100644 --- a/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx @@ -1,8 +1,9 @@ +import { css } from '@emotion/css'; import React, { FC } from 'react'; + import { FieldConfigOptionsRegistry, GrafanaTheme, ConfigOverrideRule } from '@grafana/data'; import { HorizontalGroup, Icon, IconButton, useStyles } from '@grafana/ui'; import { FieldMatcherUIRegistryItem } from '@grafana/ui/src/components/MatchersUI/types'; -import { css } from '@emotion/css'; interface OverrideCategoryTitleProps { isExpanded: boolean; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 51d91021993..59c92814742 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -1,11 +1,12 @@ +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { css } from '@emotion/css'; import { Subscription } from 'rxjs'; import { FieldConfigSource, GrafanaTheme } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { locationService } from '@grafana/runtime'; import { HorizontalGroup, InlineSwitch, @@ -15,34 +16,19 @@ import { stylesFactory, ToolbarButton, } from '@grafana/ui'; - +import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; import config from 'app/core/config'; import { appEvents } from 'app/core/core'; -import { calculatePanelSize } from './utils'; - -import { PanelEditorTabs } from './PanelEditorTabs'; -import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; -import { OptionsPane } from './OptionsPane'; import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems'; -import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; -import { SaveDashboardProxy } from '../SaveDashboard/SaveDashboardProxy'; -import { DashboardPanel } from '../../dashgrid/DashboardPanel'; - -import { discardPanelChanges, initPanelEditor, updatePanelEditorUIState } from './state/actions'; - -import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; -import { toggleTableView } from './state/reducers'; - -import { getPanelEditorTabs } from './state/selectors'; - -import { StoreState } from 'app/types'; -import { DisplayMode, displayModes, PanelEditorTab } from './types'; -import { DashboardModel, PanelModel } from '../../state'; -import { VisualizationButton } from './VisualizationButton'; -import { PanelOptionsChangedEvent, ShowModalReactEvent } from 'app/types/events'; -import { locationService } from '@grafana/runtime'; -import { UnlinkModal } from '../../../library-panels/components/UnlinkModal/UnlinkModal'; import { SaveLibraryPanelModal } from 'app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal'; +import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types'; +import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; +import { StoreState } from 'app/types'; +import { PanelOptionsChangedEvent, ShowModalReactEvent } from 'app/types/events'; + +import { notifyApp } from '../../../../core/actions'; +import { UnlinkModal } from '../../../library-panels/components/UnlinkModal/UnlinkModal'; import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; import { getLibraryPanelConnectedDashboards } from '../../../library-panels/state/api'; import { @@ -50,11 +36,21 @@ import { createPanelLibrarySuccessNotification, saveAndRefreshLibraryPanel, } from '../../../library-panels/utils'; -import { notifyApp } from '../../../../core/actions'; -import { PanelEditorTableView } from './PanelEditorTableView'; -import { PanelModelWithLibraryPanel } from 'app/features/library-panels/types'; -import { getPanelStateForModel } from 'app/features/panel/state/selectors'; import { getVariablesByKey } from '../../../variables/state/selectors'; +import { DashboardPanel } from '../../dashgrid/DashboardPanel'; +import { DashboardModel, PanelModel } from '../../state'; +import { DashNavTimeControls } from '../DashNav/DashNavTimeControls'; +import { SaveDashboardProxy } from '../SaveDashboard/SaveDashboardProxy'; + +import { OptionsPane } from './OptionsPane'; +import { PanelEditorTableView } from './PanelEditorTableView'; +import { PanelEditorTabs } from './PanelEditorTabs'; +import { VisualizationButton } from './VisualizationButton'; +import { discardPanelChanges, initPanelEditor, updatePanelEditorUIState } from './state/actions'; +import { toggleTableView } from './state/reducers'; +import { getPanelEditorTabs } from './state/selectors'; +import { DisplayMode, displayModes, PanelEditorTab } from './types'; +import { calculatePanelSize } from './utils'; interface OwnProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx index 36cc2d32b88..ca3ce045036 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx @@ -1,10 +1,12 @@ import React, { PureComponent } from 'react'; -import { QueryGroup } from 'app/features/query/components/QueryGroup'; -import { PanelModel } from '../../state'; -import { locationService } from '@grafana/runtime'; -import { QueryGroupDataSource, QueryGroupOptions } from 'app/types'; + import { DataQuery, getDataSourceRef } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { QueryGroup } from 'app/features/query/components/QueryGroup'; +import { QueryGroupDataSource, QueryGroupOptions } from 'app/types'; + +import { PanelModel } from '../../state'; interface Props { /** Current panel */ diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTableView.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTableView.tsx index 826f419ac5c..fd4958d10b4 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTableView.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTableView.tsx @@ -1,13 +1,16 @@ -import { PanelChrome } from '@grafana/ui'; -import { PanelRenderer } from 'app/features/panel/components/PanelRenderer'; import React, { useEffect, useState } from 'react'; -import { DashboardModel, PanelModel } from '../../state'; -import { usePanelLatestData } from './usePanelLatestData'; -import { PanelOptions } from 'app/plugins/panel/table/models.gen'; + import { RefreshEvent } from '@grafana/runtime'; +import { PanelChrome } from '@grafana/ui'; import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; -import { getTimeSrv } from '../../services/TimeSrv'; +import { PanelRenderer } from 'app/features/panel/components/PanelRenderer'; +import { PanelOptions } from 'app/plugins/panel/table/models.gen'; + import PanelHeaderCorner from '../../dashgrid/PanelHeader/PanelHeaderCorner'; +import { getTimeSrv } from '../../services/TimeSrv'; +import { DashboardModel, PanelModel } from '../../state'; + +import { usePanelLatestData } from './usePanelLatestData'; interface Props { width: number; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx index 8c903e3fbfa..e9c4ed1a4dc 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx @@ -1,16 +1,19 @@ -import React, { FC, useEffect } from 'react'; import { css } from '@emotion/css'; -import { IconName, Tab, TabContent, TabsBar, useForceUpdate, useStyles2 } from '@grafana/ui'; -import { TransformationsEditor } from '../TransformationsEditor/TransformationsEditor'; -import { DashboardModel, PanelModel } from '../../state'; -import { PanelEditorTab, PanelEditorTabId } from './types'; +import React, { FC, useEffect } from 'react'; import { Subscription } from 'rxjs'; -import { PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events'; -import { PanelEditorQueries } from './PanelEditorQueries'; + import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { IconName, Tab, TabContent, TabsBar, useForceUpdate, useStyles2 } from '@grafana/ui'; import AlertTabIndex from 'app/features/alerting/AlertTabIndex'; import { PanelAlertTab } from 'app/features/alerting/unified/PanelAlertTab'; +import { PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events'; + +import { DashboardModel, PanelModel } from '../../state'; +import { TransformationsEditor } from '../TransformationsEditor/TransformationsEditor'; + +import { PanelEditorQueries } from './PanelEditorQueries'; +import { PanelEditorTab, PanelEditorTabId } from './types'; interface PanelEditorTabsProps { panel: PanelModel; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx index 3140999328a..b5e6f9ed926 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.test.tsx @@ -1,7 +1,9 @@ -import { locationService } from '@grafana/runtime'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; + +import { locationService } from '@grafana/runtime'; + import { PanelNotSupported, Props } from './PanelNotSupported'; import { PanelEditorTabId } from './types'; @@ -28,9 +30,9 @@ describe('PanelNotSupported', () => { }); describe('when the back to queries button is clicked', () => { - it('then correct action should be dispatched', () => { + it('then correct action should be dispatched', async () => { setupTestContext({}); - userEvent.click(screen.getByRole('button', { name: /go back to queries/i })); + await userEvent.click(screen.getByRole('button', { name: /go back to queries/i })); expect(locationService.getSearchObject().tab).toBe(PanelEditorTabId.Query); }); }); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx index 3c0bcdcc0a6..ea60c728c8f 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx @@ -1,9 +1,10 @@ import React, { useCallback } from 'react'; -import { Button, VerticalGroup } from '@grafana/ui'; -import { Layout } from '@grafana/ui/src/components/Layout/Layout'; -import { PanelEditorTabId } from './types'; import { locationService } from '@grafana/runtime'; +import { Button, VerticalGroup } from '@grafana/ui'; +import { Layout } from '@grafana/ui/src/components/Layout/Layout'; + +import { PanelEditorTabId } from './types'; export interface Props { message: string; diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx index 8e705bbd8f2..476f77a3f3f 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx @@ -1,14 +1,17 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + import { GrafanaTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { ToolbarButton, ButtonGroup, useStyles } from '@grafana/ui'; import { StoreState } from 'app/types'; -import { useDispatch, useSelector } from 'react-redux'; -import { setPanelEditorUIState, toggleVizPicker } from './state/reducers'; -import { selectors } from '@grafana/e2e-selectors'; + import { PanelModel } from '../../state'; import { getPanelPluginWithFallback } from '../../state/selectors'; +import { setPanelEditorUIState, toggleVizPicker } from './state/reducers'; + type Props = { panel: PanelModel; }; diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx index ca2e2e18f40..db9a56901c9 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx @@ -1,21 +1,24 @@ -import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocalStorage } from 'react-use'; + import { GrafanaTheme, PanelData, SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Button, CustomScrollbar, FilterInput, RadioButtonGroup, useStyles } from '@grafana/ui'; +import { Field } from '@grafana/ui/src/components/Forms/Field'; +import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants'; +import { PanelLibraryOptionsGroup } from 'app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup'; +import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions'; +import { VizTypeChangeDetails } from 'app/features/panel/components/VizTypePicker/types'; + +import { VizTypePicker } from '../../../panel/components/VizTypePicker/VizTypePicker'; import { changePanelPlugin } from '../../../panel/state/actions'; import { PanelModel } from '../../state/PanelModel'; -import { useDispatch, useSelector } from 'react-redux'; -import { VizTypePicker } from '../../../panel/components/VizTypePicker/VizTypePicker'; -import { Field } from '@grafana/ui/src/components/Forms/Field'; -import { PanelLibraryOptionsGroup } from 'app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup'; -import { toggleVizPicker } from './state/reducers'; -import { selectors } from '@grafana/e2e-selectors'; import { getPanelPluginWithFallback } from '../../state/selectors'; -import { VizTypeChangeDetails } from 'app/features/panel/components/VizTypePicker/types'; -import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions'; -import { useLocalStorage } from 'react-use'; + +import { toggleVizPicker } from './state/reducers'; import { VisualizationSelectPaneTab } from './types'; -import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants'; interface Props { panel: PanelModel; diff --git a/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx b/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx index 43af045b55e..9ca84fdf48a 100644 --- a/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx @@ -1,5 +1,7 @@ -import React from 'react'; +import { css } from '@emotion/css'; import { cloneDeep } from 'lodash'; +import React from 'react'; + import { FieldConfigOptionsRegistry, SelectableValue, @@ -10,13 +12,13 @@ import { GrafanaTheme2, } from '@grafana/data'; import { fieldMatchersUI, useStyles2, ValuePicker } from '@grafana/ui'; -import { OptionPaneRenderProps } from './types'; -import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; -import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; -import { DynamicConfigValueEditor } from './DynamicConfigValueEditor'; import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; + +import { DynamicConfigValueEditor } from './DynamicConfigValueEditor'; +import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; import { OverrideCategoryTitle } from './OverrideCategoryTitle'; -import { css } from '@emotion/css'; +import { OptionPaneRenderProps } from './types'; export function getFieldOverrideCategories( props: OptionPaneRenderProps, diff --git a/public/app/features/dashboard/components/PanelEditor/getLibraryPanelOptions.tsx b/public/app/features/dashboard/components/PanelEditor/getLibraryPanelOptions.tsx index d92669f3f72..8f1036efcb4 100644 --- a/public/app/features/dashboard/components/PanelEditor/getLibraryPanelOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getLibraryPanelOptions.tsx @@ -1,11 +1,14 @@ -import { Input } from '@grafana/ui'; import React from 'react'; -import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; -import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; -import { OptionPaneRenderProps } from './types'; -import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; + +import { Input } from '@grafana/ui'; import { LibraryPanelInformation } from 'app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo'; +import { isPanelModelLibraryPanel } from '../../../library-panels/guard'; + +import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; +import { OptionPaneRenderProps } from './types'; + export function getLibraryPanelOptionsCategory(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor { const { panel, onPanelConfigChange, dashboard } = props; const descriptor = new OptionsPaneCategoryDescriptor({ diff --git a/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx b/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx index 5cd1e34348b..20e4a7a69b7 100644 --- a/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getPanelFrameOptions.tsx @@ -1,9 +1,12 @@ +import React from 'react'; + import { DataLinksInlineEditor, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui'; import { getPanelLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; -import React from 'react'; + import { RepeatRowSelect } from '../RepeatRowSelect/RepeatRowSelect'; -import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; + import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; import { OptionPaneRenderProps } from './types'; export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor { diff --git a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts index 3d3c461f6f8..01f12b81f1c 100644 --- a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts @@ -1,4 +1,5 @@ import { EventBusSrv, FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data'; + import { getStandardEditorContext } from './getVisualizationOptions'; describe('getStandardEditorContext', () => { diff --git a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx index be266ae7e9b..a3fa3263e73 100644 --- a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx @@ -1,4 +1,6 @@ +import { get as lodashGet } from 'lodash'; import React from 'react'; + import { EventBus, InterpolateFunction, @@ -6,19 +8,19 @@ import { StandardEditorContext, VariableSuggestionsScope, } from '@grafana/data'; -import { get as lodashGet } from 'lodash'; -import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; -import { OptionPaneRenderProps } from './types'; -import { setOptionImmutably, updateDefaultFieldConfigValue } from './utils'; -import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; -import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; +import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; import { isNestedPanelOptions, NestedValueAccess, PanelOptionsEditorBuilder, } from '@grafana/data/src/utils/OptionsUIBuilders'; -import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; +import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv'; + +import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor'; import { getOptionOverrides } from './state/getOptionOverrides'; +import { OptionPaneRenderProps } from './types'; +import { setOptionImmutably, updateDefaultFieldConfigValue } from './utils'; type categoryGetter = (categoryNames?: string[]) => OptionsPaneCategoryDescriptor; diff --git a/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.test.ts b/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.test.ts index bb9d42bc0d7..43787af8f58 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.test.ts @@ -1,5 +1,6 @@ import { OptionsPaneCategoryDescriptor } from '../OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from '../OptionsPaneItemDescriptor'; + import { OptionSearchEngine } from './OptionSearchEngine'; describe('OptionSearchEngine', () => { diff --git a/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.ts b/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.ts index c29f8399949..c861806702d 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/OptionSearchEngine.ts @@ -1,5 +1,5 @@ -import { OptionsPaneItemDescriptor } from '../OptionsPaneItemDescriptor'; import { OptionsPaneCategoryDescriptor } from '../OptionsPaneCategoryDescriptor'; +import { OptionsPaneItemDescriptor } from '../OptionsPaneItemDescriptor'; export interface OptionSearchResults { optionHits: OptionsPaneItemDescriptor[]; diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts index 1e864f160ac..b52df7d3ae9 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.test.ts @@ -1,10 +1,12 @@ -import { thunkTester } from '../../../../../../test/core/thunk/thunkTester'; -import { closeEditor, initialState, PanelEditorState } from './reducers'; -import { exitPanelEditor, initPanelEditor, skipPanelUpdate } from './actions'; import { panelModelAndPluginReady, removePanel } from 'app/features/panel/state/reducers'; -import { DashboardModel, PanelModel } from '../../../state'; import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; +import { thunkTester } from '../../../../../../test/core/thunk/thunkTester'; +import { DashboardModel, PanelModel } from '../../../state'; + +import { exitPanelEditor, initPanelEditor, skipPanelUpdate } from './actions'; +import { closeEditor, initialState, PanelEditorState } from './reducers'; + describe('panelEditor actions', () => { describe('initPanelEditor', () => { it('initPanelEditor should create edit panel model as clone', async () => { diff --git a/public/app/features/dashboard/components/PanelEditor/state/actions.ts b/public/app/features/dashboard/components/PanelEditor/state/actions.ts index 7093dd47cdc..a2c037843eb 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/actions.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/actions.ts @@ -1,8 +1,12 @@ import { pick } from 'lodash'; -import { ThunkResult } from 'app/types'; + import store from 'app/core/store'; -import { panelModelAndPluginReady } from 'app/features/panel/state/reducers'; import { cleanUpPanelState, initPanelState } from 'app/features/panel/state/actions'; +import { panelModelAndPluginReady } from 'app/features/panel/state/reducers'; +import { ThunkResult } from 'app/types'; + +import { DashboardModel, PanelModel } from '../../../state'; + import { closeEditor, PANEL_EDITOR_UI_STATE_STORAGE_KEY, @@ -11,7 +15,6 @@ import { setPanelEditorUIState, updateEditorInitState, } from './reducers'; -import { DashboardModel, PanelModel } from '../../../state'; export function initPanelEditor(sourcePanel: PanelModel, dashboard: DashboardModel): ThunkResult { return async (dispatch) => { diff --git a/public/app/features/dashboard/components/PanelEditor/state/getOptionOverrides.ts b/public/app/features/dashboard/components/PanelEditor/state/getOptionOverrides.ts index 7160401a861..0ce4e9ce71b 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/getOptionOverrides.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/getOptionOverrides.ts @@ -1,5 +1,7 @@ -import { DataFrame, FieldConfigPropertyItem, FieldConfigSource } from '@grafana/data'; import { get as lodashGet } from 'lodash'; + +import { DataFrame, FieldConfigPropertyItem, FieldConfigSource } from '@grafana/data'; + import { OptionPaneItemOverrideInfo } from '../types'; export const dataOverrideTooltipDescription = diff --git a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts index b8e77139e78..9a366235f2f 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/reducers.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/reducers.ts @@ -1,8 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { PanelModel } from '../../../state/PanelModel'; + import { getDefaultTimeRange, LoadingState, PanelData } from '@grafana/data'; -import { DisplayMode } from '../types'; + import store from '../../../../../core/store'; +import { PanelModel } from '../../../state/PanelModel'; +import { DisplayMode } from '../types'; export const PANEL_EDITOR_UI_STATE_STORAGE_KEY = 'grafana.dashboard.editor.ui'; diff --git a/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts b/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts index 0bab17d32f9..86f37cb761a 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts @@ -1,9 +1,11 @@ -import { getPanelEditorTabs } from './selectors'; import { PanelPlugin } from '@grafana/data'; -import { PanelEditorTabId } from '../types'; -import { updateConfig } from '../../../../../core/config'; import { contextSrv } from 'app/core/services/context_srv'; +import { updateConfig } from '../../../../../core/config'; +import { PanelEditorTabId } from '../types'; + +import { getPanelEditorTabs } from './selectors'; + jest.mock('app/core/services/context_srv'); const mocks = { diff --git a/public/app/features/dashboard/components/PanelEditor/state/selectors.ts b/public/app/features/dashboard/components/PanelEditor/state/selectors.ts index 007945700da..7627bcbcf07 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/selectors.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/selectors.ts @@ -1,11 +1,13 @@ import memoizeOne from 'memoize-one'; + import { PanelPlugin } from '@grafana/data'; -import { PanelEditorTab, PanelEditorTabId } from '../types'; import { getConfig } from 'app/core/config'; -import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control'; import { contextSrv } from 'app/core/services/context_srv'; +import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control'; import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; +import { PanelEditorTab, PanelEditorTabId } from '../types'; + export const getPanelEditorTabs = memoizeOne((tab?: string, plugin?: PanelPlugin) => { const tabs: PanelEditorTab[] = []; diff --git a/public/app/features/dashboard/components/PanelEditor/types.ts b/public/app/features/dashboard/components/PanelEditor/types.ts index 6e4b2306637..39d8f6409bd 100644 --- a/public/app/features/dashboard/components/PanelEditor/types.ts +++ b/public/app/features/dashboard/components/PanelEditor/types.ts @@ -1,4 +1,5 @@ import { DataFrame, FieldConfigSource, PanelData, PanelPlugin } from '@grafana/data'; + import { DashboardModel, PanelModel } from '../../state'; export interface PanelEditorTab { diff --git a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts index 625642f43a5..f3e2aa5f2d2 100644 --- a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts +++ b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts @@ -1,8 +1,10 @@ -import { DataQueryError, LoadingState, PanelData } from '@grafana/data'; import { useEffect, useRef, useState } from 'react'; -import { PanelModel } from '../../state'; import { Unsubscribable } from 'rxjs'; + +import { DataQueryError, LoadingState, PanelData } from '@grafana/data'; + import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; +import { PanelModel } from '../../state'; interface UsePanelLatestData { data?: PanelData; diff --git a/public/app/features/dashboard/components/PanelEditor/utils.test.ts b/public/app/features/dashboard/components/PanelEditor/utils.test.ts index ba19f093308..b2efaa7aa7f 100644 --- a/public/app/features/dashboard/components/PanelEditor/utils.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/utils.test.ts @@ -1,4 +1,5 @@ import { FieldConfig, FieldConfigSource, PanelPlugin, standardFieldConfigEditorRegistry } from '@grafana/data'; + import { setOptionImmutably, supportsDataQuery, updateDefaultFieldConfigValue } from './utils'; describe('standardFieldConfigEditorRegistry', () => { diff --git a/public/app/features/dashboard/components/PanelEditor/utils.ts b/public/app/features/dashboard/components/PanelEditor/utils.ts index 868476b6241..d5a3342e668 100644 --- a/public/app/features/dashboard/components/PanelEditor/utils.ts +++ b/public/app/features/dashboard/components/PanelEditor/utils.ts @@ -1,9 +1,12 @@ import { omit } from 'lodash'; + import { FieldConfigSource, PanelPlugin } from '@grafana/data'; -import { PanelModel } from '../../state/PanelModel'; -import { DisplayMode } from './types'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; +import { PanelModel } from '../../state/PanelModel'; + +import { DisplayMode } from './types'; + export function calculatePanelSize(mode: DisplayMode, width: number, height: number, panel: PanelModel) { if (mode === DisplayMode.Fill) { return { width, height }; diff --git a/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx b/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx index 0ad229d26df..596dd425654 100644 --- a/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx +++ b/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx @@ -1,10 +1,11 @@ import React, { FC, useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { getLastKey, getVariablesByKey } from '../../../variables/state/selectors'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { StoreState } from '../../../../types'; +import { getLastKey, getVariablesByKey } from '../../../variables/state/selectors'; export interface Props { id?: string; diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx index 417eeb3a795..fa49740bc0e 100644 --- a/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsButton.tsx @@ -1,8 +1,9 @@ import React, { FC } from 'react'; + import { Icon, ModalsController } from '@grafana/ui'; -import { RowOptionsModal } from './RowOptionsModal'; import { OnRowOptionsUpdate } from './RowOptionsForm'; +import { RowOptionsModal } from './RowOptionsModal'; export interface RowOptionsButtonProps { title: string; diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx index 1f841528a90..40b9aafaabb 100644 --- a/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsForm.tsx @@ -1,4 +1,5 @@ import React, { FC, useCallback, useState } from 'react'; + import { Button, Field, Form, Modal, Input } from '@grafana/ui'; import { RepeatRowSelect } from '../RepeatRowSelect/RepeatRowSelect'; diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx index 7e0555b3744..785d10f89f2 100644 --- a/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx @@ -1,6 +1,7 @@ -import React, { FC } from 'react'; -import { Modal, stylesFactory } from '@grafana/ui'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { Modal, stylesFactory } from '@grafana/ui'; import { OnRowOptionsUpdate, RowOptionsForm } from './RowOptionsForm'; diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardAsModal.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardAsModal.tsx index ca9c0364b7c..785ee7b45ed 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardAsModal.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardAsModal.tsx @@ -1,10 +1,12 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; + import { Modal } from '@grafana/ui'; -import { SaveDashboardAsForm } from './forms/SaveDashboardAsForm'; + import { SaveDashboardErrorProxy } from './SaveDashboardErrorProxy'; -import { useDashboardSave } from './useDashboardSave'; +import { SaveDashboardAsForm } from './forms/SaveDashboardAsForm'; import { SaveDashboardModalProps } from './types'; +import { useDashboardSave } from './useDashboardSave'; export const SaveDashboardAsModal: React.FC< SaveDashboardModalProps & { diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx index d754fe352a4..75d09010f9a 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx @@ -1,8 +1,10 @@ import React from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; import { Button, ButtonVariant, ModalsController, FullWidthButtonContainer } from '@grafana/ui'; import { DashboardModel } from 'app/features/dashboard/state'; + import { SaveDashboardProxy } from './SaveDashboardProxy'; -import { selectors } from '@grafana/e2e-selectors'; interface SaveDashboardButtonProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDiff.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDiff.tsx index ba26e0ba1b2..6deb43117f4 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDiff.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDiff.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; +import { useAsync } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Spinner, useStyles2 } from '@grafana/ui'; -import { Diffs } from '../VersionHistory/utils'; + import { DiffGroup } from '../VersionHistory/DiffGroup'; import { DiffViewer } from '../VersionHistory/DiffViewer'; -import { GrafanaTheme2 } from '@grafana/data'; -import { useAsync } from 'react-use'; +import { Diffs } from '../VersionHistory/utils'; interface SaveDashboardDiffProps { oldValue?: any; diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx index 798dd6a333e..5c37e6389d6 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx @@ -1,15 +1,18 @@ import React, { useMemo, useState } from 'react'; -import { Drawer, Tab, TabsBar } from '@grafana/ui'; -import { SaveDashboardData, SaveDashboardModalProps, SaveDashboardOptions } from './types'; -import { jsonDiff } from '../VersionHistory/utils'; import { useAsync } from 'react-use'; + +import { Drawer, Tab, TabsBar } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; -import { useDashboardSave } from './useDashboardSave'; -import { SaveProvisionedDashboardForm } from './forms/SaveProvisionedDashboardForm'; + +import { jsonDiff } from '../VersionHistory/utils'; + +import { SaveDashboardDiff } from './SaveDashboardDiff'; import { SaveDashboardErrorProxy } from './SaveDashboardErrorProxy'; import { SaveDashboardAsForm } from './forms/SaveDashboardAsForm'; import { SaveDashboardForm } from './forms/SaveDashboardForm'; -import { SaveDashboardDiff } from './SaveDashboardDiff'; +import { SaveProvisionedDashboardForm } from './forms/SaveProvisionedDashboardForm'; +import { SaveDashboardData, SaveDashboardModalProps, SaveDashboardOptions } from './types'; +import { useDashboardSave } from './useDashboardSave'; export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCopy }: SaveDashboardModalProps) => { const [options, setOptions] = useState({}); diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx index a1c1df7425f..cde2d55f1a1 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx @@ -1,11 +1,13 @@ -import React, { useEffect } from 'react'; -import { Button, ConfirmModal, Modal, stylesFactory, useTheme } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { useEffect } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { Button, ConfirmModal, Modal, stylesFactory, useTheme } from '@grafana/ui'; import { DashboardModel } from 'app/features/dashboard/state'; -import { useDashboardSave } from './useDashboardSave'; -import { SaveDashboardModalProps } from './types'; + import { SaveDashboardAsButton } from './SaveDashboardButton'; +import { SaveDashboardModalProps } from './types'; +import { useDashboardSave } from './useDashboardSave'; interface SaveDashboardErrorProxyProps { /** original dashboard */ diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardModal.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardModal.tsx index 42d4ac53a99..231d1d5021a 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardModal.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardModal.tsx @@ -1,10 +1,12 @@ -import React, { useMemo, useState } from 'react'; -import { Modal } from '@grafana/ui'; import { css } from '@emotion/css'; -import { SaveDashboardForm } from './forms/SaveDashboardForm'; +import React, { useMemo, useState } from 'react'; + +import { Modal } from '@grafana/ui'; + import { SaveDashboardErrorProxy } from './SaveDashboardErrorProxy'; -import { useDashboardSave } from './useDashboardSave'; +import { SaveDashboardForm } from './forms/SaveDashboardForm'; import { SaveDashboardModalProps, SaveDashboardOptions, SaveDashboardData } from './types'; +import { useDashboardSave } from './useDashboardSave'; export const SaveDashboardModal: React.FC = ({ dashboard, onDismiss, onSaveSuccess }) => { const { state, onDashboardSave } = useDashboardSave(dashboard); diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardProxy.tsx index ba9288672b4..50d149bfce3 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardProxy.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import { SaveProvisionedDashboard } from './SaveProvisionedDashboard'; -import { SaveDashboardAsModal } from './SaveDashboardAsModal'; -import { SaveDashboardModalProps } from './types'; -import { SaveDashboardModal } from './SaveDashboardModal'; + import { config } from '@grafana/runtime'; + +import { SaveDashboardAsModal } from './SaveDashboardAsModal'; import { SaveDashboardDrawer } from './SaveDashboardDrawer'; +import { SaveDashboardModal } from './SaveDashboardModal'; +import { SaveProvisionedDashboard } from './SaveProvisionedDashboard'; +import { SaveDashboardModalProps } from './types'; export const SaveDashboardProxy: React.FC = ({ dashboard, diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveProvisionedDashboard.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveProvisionedDashboard.tsx index 135825fe910..cedb83c9948 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveProvisionedDashboard.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveProvisionedDashboard.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { Modal } from '@grafana/ui'; + import { SaveProvisionedDashboardForm } from './forms/SaveProvisionedDashboardForm'; import { SaveDashboardModalProps } from './types'; diff --git a/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx b/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx index fa0eaa4cf19..e7b63939af9 100644 --- a/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx @@ -1,8 +1,11 @@ -import React from 'react'; -import { Button, Modal } from '@grafana/ui'; -import { SaveDashboardButton } from './SaveDashboardButton'; -import { DashboardModel } from '../../state'; import { css } from '@emotion/css'; +import React from 'react'; + +import { Button, Modal } from '@grafana/ui'; + +import { DashboardModel } from '../../state'; + +import { SaveDashboardButton } from './SaveDashboardButton'; interface UnsavedChangesModalProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx index d58801a2c68..4a36443114c 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { SaveDashboardAsForm, SaveDashboardAsFormProps } from './SaveDashboardAsForm'; -import { DashboardModel } from 'app/features/dashboard/state'; +import React from 'react'; import { act } from 'react-dom/test-utils'; + +import { DashboardModel } from 'app/features/dashboard/state'; import * as api from 'app/features/manage-dashboards/state/actions'; +import { SaveDashboardAsForm, SaveDashboardAsFormProps } from './SaveDashboardAsForm'; + jest.mock('app/features/plugins/datasource_srv', () => ({})); jest.mock('app/features/expressions/ExpressionDatasource', () => ({})); jest.mock('app/features/manage-dashboards/services/ValidationSrv', () => ({ diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx index a17b651089a..b1176a0808e 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx @@ -1,10 +1,12 @@ import React from 'react'; + import { Button, Input, Switch, Form, Field, InputControl, HorizontalGroup } from '@grafana/ui'; -import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; -import { SaveDashboardFormProps } from '../types'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv'; +import { SaveDashboardFormProps } from '../types'; + interface SaveDashboardAsFormDTO { title: string; $folder: { id?: number; title?: string }; diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx index ff7a020d637..8a5b60159d9 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; import { act } from 'react-dom/test-utils'; + import { DashboardModel } from 'app/features/dashboard/state'; -import { SaveDashboardForm } from './SaveDashboardForm'; + import { SaveDashboardOptions } from '../types'; +import { SaveDashboardForm } from './SaveDashboardForm'; + const prepareDashboardMock = ( timeChanged: boolean, variableValuesChanged: boolean, diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx index 37463b0c85e..1bae42d0dab 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx @@ -1,11 +1,11 @@ import React, { useMemo, useState } from 'react'; -import { Button, Checkbox, Form, TextArea } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; - -import { DashboardModel } from 'app/features/dashboard/state'; -import { SaveDashboardData, SaveDashboardOptions } from '../types'; import { Stack } from '@grafana/experimental'; +import { Button, Checkbox, Form, TextArea } from '@grafana/ui'; +import { DashboardModel } from 'app/features/dashboard/state'; + +import { SaveDashboardData, SaveDashboardOptions } from '../types'; interface FormDTO { message: string; diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx index da03cbf3ce2..8fc0075998e 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx @@ -1,11 +1,13 @@ -import React, { useCallback, useState } from 'react'; import { css } from '@emotion/css'; import { saveAs } from 'file-saver'; -import { Button, ClipboardButton, HorizontalGroup, stylesFactory, TextArea, useTheme } from '@grafana/ui'; -import { SaveDashboardFormProps } from '../types'; +import React, { useCallback, useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { useAppNotification } from 'app/core/copy/appNotification'; import { Stack } from '@grafana/experimental'; +import { Button, ClipboardButton, HorizontalGroup, stylesFactory, TextArea, useTheme } from '@grafana/ui'; +import { useAppNotification } from 'app/core/copy/appNotification'; + +import { SaveDashboardFormProps } from '../types'; export const SaveProvisionedDashboardForm: React.FC = ({ dashboard, onCancel }) => { const theme = useTheme(); diff --git a/public/app/features/dashboard/components/SaveDashboard/types.ts b/public/app/features/dashboard/components/SaveDashboard/types.ts index a8783ff6b69..e0ab7e9c8c5 100644 --- a/public/app/features/dashboard/components/SaveDashboard/types.ts +++ b/public/app/features/dashboard/components/SaveDashboard/types.ts @@ -1,4 +1,5 @@ import { CloneOptions, DashboardModel } from 'app/features/dashboard/state/DashboardModel'; + import { Diffs } from '../VersionHistory/utils'; export interface SaveDashboardData { diff --git a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx index d6675c9dd97..6c4237f855f 100644 --- a/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx @@ -1,15 +1,17 @@ import { useEffect } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; + import { locationUtil } from '@grafana/data'; -import { SaveDashboardOptions } from './types'; +import { locationService, reportInteraction } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; -import { contextSrv } from 'app/core/core'; import { useAppNotification } from 'app/core/copy/appNotification'; +import { contextSrv } from 'app/core/core'; import { DashboardModel } from 'app/features/dashboard/state'; import { saveDashboard as saveDashboardApiCall } from 'app/features/manage-dashboards/state/actions'; -import { locationService, reportInteraction } from '@grafana/runtime'; import { DashboardSavedEvent } from 'app/types/events'; +import { SaveDashboardOptions } from './types'; + const saveDashboard = async (saveModel: any, options: SaveDashboardOptions, dashboard: DashboardModel) => { let folderId = options.folderId; if (folderId === undefined) { diff --git a/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx index 22d9e22ad75..ac1293a487c 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareEmbed.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import { ShareEmbed } from './ShareEmbed'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import config from 'app/core/config'; + import { DashboardModel, PanelModel } from '../../state'; +import { ShareEmbed } from './ShareEmbed'; + jest.mock('app/features/dashboard/services/TimeSrv', () => ({ getTimeSrv: () => ({ timeRange: () => { diff --git a/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx b/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx index f1c97a727a8..1262ee2dfde 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx @@ -1,9 +1,11 @@ import React, { FormEvent, PureComponent } from 'react'; -import { ClipboardButton, Field, Modal, RadioButtonGroup, Switch, TextArea } from '@grafana/ui'; + import { AppEvents, SelectableValue } from '@grafana/data'; +import { ClipboardButton, Field, Modal, RadioButtonGroup, Switch, TextArea } from '@grafana/ui'; import { appEvents } from 'app/core/core'; -import { buildIframeHtml } from './utils'; + import { ShareModalTabProps } from './types'; +import { buildIframeHtml } from './utils'; const themeOptions: Array> = [ { label: 'Current', value: 'current' }, diff --git a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx index c687a86f89a..934b233eda1 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx @@ -1,12 +1,14 @@ -import React, { PureComponent } from 'react'; import { saveAs } from 'file-saver'; -import { getBackendSrv } from 'app/core/services/backend_srv'; -import { Button, Field, Modal, Switch } from '@grafana/ui'; -import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; -import { appEvents } from 'app/core/core'; -import { ShowModalReactEvent } from 'app/types/events'; -import { ViewJsonModal } from './ViewJsonModal'; +import React, { PureComponent } from 'react'; + import { config } from '@grafana/runtime'; +import { Button, Field, Modal, Switch } from '@grafana/ui'; +import { appEvents } from 'app/core/core'; +import { getBackendSrv } from 'app/core/services/backend_srv'; +import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; +import { ShowModalReactEvent } from 'app/types/events'; + +import { ViewJsonModal } from './ViewJsonModal'; import { ShareModalTabProps } from './types'; interface Props extends ShareModalTabProps {} diff --git a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx index a9b5ace06c9..db91c703b43 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { AddLibraryPanelContents } from 'app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal'; + import { ShareModalTabProps } from './types'; interface Props extends ShareModalTabProps { diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx index b8ec81aa3d3..a8c1ac5ea84 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx @@ -1,13 +1,16 @@ -import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; +import React from 'react'; + +import { getDefaultTimeRange } from '@grafana/data'; import { setTemplateSrv } from '@grafana/runtime'; import config from 'app/core/config'; -import { Props, ShareLink, State } from './ShareLink'; + import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv'; import { variableAdapters } from '../../../variables/adapters'; import { createQueryVariableAdapter } from '../../../variables/query/adapter'; import { DashboardModel, PanelModel } from '../../state'; -import { getDefaultTimeRange } from '@grafana/data'; + +import { Props, ShareLink, State } from './ShareLink'; jest.mock('app/features/dashboard/services/TimeSrv', () => ({ getTimeSrv: () => ({ diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx index b14b485a0cc..0b937f4cf6b 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx @@ -1,11 +1,13 @@ import React, { PureComponent } from 'react'; + +import { AppEvents, SelectableValue } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, RadioButtonGroup, Switch } from '@grafana/ui'; -import { AppEvents, SelectableValue } from '@grafana/data'; -import { buildImageUrl, buildShareUrl } from './utils'; -import { appEvents } from 'app/core/core'; import config from 'app/core/config'; +import { appEvents } from 'app/core/core'; + import { ShareModalTabProps } from './types'; +import { buildImageUrl, buildShareUrl } from './utils'; const themeOptions: Array> = [ { label: 'Current', value: 'current' }, diff --git a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx index 984d77ce8bf..bc4945f416a 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx @@ -1,14 +1,16 @@ import React from 'react'; + import { Modal, ModalTabsHeader, TabContent } from '@grafana/ui'; +import { contextSrv } from 'app/core/core'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard'; + +import { ShareEmbed } from './ShareEmbed'; +import { ShareExport } from './ShareExport'; +import { ShareLibraryPanel } from './ShareLibraryPanel'; import { ShareLink } from './ShareLink'; import { ShareSnapshot } from './ShareSnapshot'; -import { ShareExport } from './ShareExport'; -import { ShareEmbed } from './ShareEmbed'; import { ShareModalTabModel } from './types'; -import { contextSrv } from 'app/core/core'; -import { ShareLibraryPanel } from './ShareLibraryPanel'; const customDashboardTabs: ShareModalTabModel[] = []; const customPanelTabs: ShareModalTabModel[] = []; diff --git a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx index 1ac5b0d4d19..91d552a0195 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx @@ -1,11 +1,14 @@ import React, { PureComponent } from 'react'; -import { Button, ClipboardButton, Field, Icon, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui'; + import { AppEvents, SelectableValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { Button, ClipboardButton, Field, Icon, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui'; import { appEvents } from 'app/core/core'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; + import { VariableRefresh } from '../../../variables/types'; + import { ShareModalTabProps } from './types'; const snapshotApiUrl = '/api/snapshots'; diff --git a/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx b/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx index 272d60057e1..fc5f35cb7fe 100644 --- a/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx +++ b/public/app/features/dashboard/components/ShareModal/ViewJsonModal.tsx @@ -1,10 +1,11 @@ import React, { useCallback } from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; + import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui'; -import AutoSizer from 'react-virtualized-auto-sizer'; import { notifyApp } from '../../../../core/actions'; -import { dispatch } from '../../../../store/store'; import { createSuccessNotification } from '../../../../core/copy/appNotification'; +import { dispatch } from '../../../../store/store'; export interface ViewJsonModalProps { json: string; diff --git a/public/app/features/dashboard/components/ShareModal/types.ts b/public/app/features/dashboard/components/ShareModal/types.ts index 42d45c9ea89..558bd0fa8a9 100644 --- a/public/app/features/dashboard/components/ShareModal/types.ts +++ b/public/app/features/dashboard/components/ShareModal/types.ts @@ -1,4 +1,5 @@ import React from 'react'; + import { NavModelItem } from '@grafana/data'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; diff --git a/public/app/features/dashboard/components/ShareModal/utils.test.ts b/public/app/features/dashboard/components/ShareModal/utils.test.ts index 5b359e1ea2a..8e2a42d553a 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.test.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.test.ts @@ -1,6 +1,7 @@ -import { buildParams } from './utils'; import { TimeRange } from '@grafana/data'; +import { buildParams } from './utils'; + describe('buildParams', () => { it.each` search | useCurrentTimeRange | selectedTheme | panel | expected diff --git a/public/app/features/dashboard/components/ShareModal/utils.ts b/public/app/features/dashboard/components/ShareModal/utils.ts index 4667ec23ae1..fb350ce464a 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.ts @@ -1,7 +1,7 @@ -import { config } from '@grafana/runtime'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { createShortLink } from 'app/core/utils/shortLinks'; import { dateTime, locationUtil, PanelModel, TimeRange, urlUtil } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { createShortLink } from 'app/core/utils/shortLinks'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; export interface BuildParamsArgs { useCurrentTimeRange: boolean; diff --git a/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx b/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx index dda2461e908..ed9a0b3f38c 100644 --- a/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx +++ b/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx @@ -1,10 +1,12 @@ -import { AnnotationQuery, EventBus, GrafanaTheme2 } from '@grafana/data'; +import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; -import { getDashboardQueryRunner } from '../../../query/state/DashboardQueryRunner/DashboardQueryRunner'; -import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events'; + +import { AnnotationQuery, EventBus, GrafanaTheme2 } from '@grafana/data'; import { InlineField, InlineFieldRow, InlineSwitch, useStyles2 } from '@grafana/ui'; import { LoadingIndicator } from '@grafana/ui/src/components/PanelChrome/LoadingIndicator'; -import { css } from '@emotion/css'; + +import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events'; +import { getDashboardQueryRunner } from '../../../query/state/DashboardQueryRunner/DashboardQueryRunner'; export interface AnnotationPickerProps { events: EventBus; diff --git a/public/app/features/dashboard/components/SubMenu/Annotations.tsx b/public/app/features/dashboard/components/SubMenu/Annotations.tsx index 8f0d30afe5d..a08f235341a 100644 --- a/public/app/features/dashboard/components/SubMenu/Annotations.tsx +++ b/public/app/features/dashboard/components/SubMenu/Annotations.tsx @@ -1,5 +1,7 @@ import React, { FunctionComponent, useEffect, useState } from 'react'; + import { AnnotationQuery, EventBus } from '@grafana/data'; + import { AnnotationPicker } from './AnnotationPicker'; interface Props { diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx index 2cc9bd5b752..b86b66651c8 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinks.tsx @@ -1,15 +1,17 @@ import React, { FC } from 'react'; -import { Icon, IconName, Tooltip, useForceUpdate } from '@grafana/ui'; -import { sanitizeUrl } from '@grafana/data/src/text/sanitize'; -import { DashboardLinksDashboard } from './DashboardLinksDashboard'; -import { getLinkSrv } from '../../../panel/panellinks/link_srv'; +import { useEffectOnce } from 'react-use'; +import { sanitizeUrl } from '@grafana/data/src/text/sanitize'; +import { selectors } from '@grafana/e2e-selectors'; +import { TimeRangeUpdatedEvent } from '@grafana/runtime'; +import { Icon, IconName, Tooltip, useForceUpdate } from '@grafana/ui'; + +import { getLinkSrv } from '../../../panel/panellinks/link_srv'; import { DashboardModel } from '../../state'; import { DashboardLink } from '../../state/DashboardModel'; import { linkIconMap } from '../LinksSettings/LinkSettingsEdit'; -import { useEffectOnce } from 'react-use'; -import { selectors } from '@grafana/e2e-selectors'; -import { TimeRangeUpdatedEvent } from '@grafana/runtime'; + +import { DashboardLinksDashboard } from './DashboardLinksDashboard'; export interface Props { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx index 23d17ef61da..8586b726640 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx @@ -1,7 +1,8 @@ -import { DashboardLink } from '../../state/DashboardModel'; -import { DashboardSearchHit, DashboardSearchItemType } from '../../../search/types'; -import { resolveLinks, searchForTags } from './DashboardLinksDashboard'; import { describe, expect } from '../../../../../test/lib/common'; +import { DashboardSearchHit, DashboardSearchItemType } from '../../../search/types'; +import { DashboardLink } from '../../state/DashboardModel'; + +import { resolveLinks, searchForTags } from './DashboardLinksDashboard'; describe('searchForTags', () => { const setupTestContext = () => { diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index 203af35cf60..53ccd630a0b 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -1,13 +1,15 @@ +import { css, cx } from '@emotion/css'; import React, { useRef, useState, useLayoutEffect } from 'react'; -import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui'; +import { useAsync } from 'react-use'; + import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize'; +import { selectors } from '@grafana/e2e-selectors'; +import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { DashboardSearchHit } from 'app/features/search/types'; + import { getLinkSrv } from '../../../panel/panellinks/link_srv'; import { DashboardLink } from '../../state/DashboardModel'; -import { DashboardSearchHit } from 'app/features/search/types'; -import { selectors } from '@grafana/e2e-selectors'; -import { useAsync } from 'react-use'; -import { css, cx } from '@emotion/css'; interface Props { link: DashboardLink; diff --git a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx index 14839796da0..a22a3e4a280 100644 --- a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx +++ b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx @@ -1,15 +1,18 @@ +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; import { connect, MapStateToProps } from 'react-redux'; + +import { AnnotationQuery } from '@grafana/data'; + import { StoreState } from '../../../../types'; import { getSubMenuVariables, getVariablesState } from '../../../variables/state/selectors'; import { VariableModel } from '../../../variables/types'; import { DashboardModel } from '../../state'; -import { DashboardLinks } from './DashboardLinks'; -import { Annotations } from './Annotations'; -import { SubMenuItems } from './SubMenuItems'; import { DashboardLink } from '../../state/DashboardModel'; -import { AnnotationQuery } from '@grafana/data'; -import { css } from '@emotion/css'; + +import { Annotations } from './Annotations'; +import { DashboardLinks } from './DashboardLinks'; +import { SubMenuItems } from './SubMenuItems'; interface OwnProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx b/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx index 72f15ba4db8..7bc29f1721a 100644 --- a/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx +++ b/public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx @@ -1,7 +1,9 @@ import React, { FunctionComponent, useEffect, useState } from 'react'; -import { VariableHide, VariableModel } from '../../../variables/types'; + import { selectors } from '@grafana/e2e-selectors'; + import { PickerRenderer } from '../../../variables/pickers/PickerRenderer'; +import { VariableHide, VariableModel } from '../../../variables/types'; interface Props { variables: VariableModel[]; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx index 64b626a5a1b..f1d99bd708c 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx @@ -1,7 +1,7 @@ +import { css } from '@emotion/css'; import React, { useEffect, useMemo, useState } from 'react'; import { mergeMap } from 'rxjs/operators'; -import { css } from '@emotion/css'; -import { Icon, JSONFormatter, useStyles } from '@grafana/ui'; + import { DataFrame, DataTransformerConfig, @@ -10,6 +10,7 @@ import { TransformerRegistryItem, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { Icon, JSONFormatter, useStyles } from '@grafana/ui'; import { TransformationsEditorTransformation } from './types'; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx index c2d82b330d7..71eeb0200e1 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx @@ -1,17 +1,18 @@ import React, { useCallback } from 'react'; +import { useToggle } from 'react-use'; + import { DataFrame, DataTransformerConfig, TransformerRegistryItem } from '@grafana/data'; import { HorizontalGroup } from '@grafana/ui'; - -import { TransformationEditor } from './TransformationEditor'; +import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; +import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { QueryOperationRow, QueryOperationRowRenderProps, } from 'app/core/components/QueryOperationRow/QueryOperationRow'; -import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; -import { TransformationsEditorTransformation } from './types'; import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; -import { useToggle } from 'react-use'; -import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; + +import { TransformationEditor } from './TransformationEditor'; +import { TransformationsEditorTransformation } from './types'; interface TransformationOperationRowProps { id: string; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRows.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRows.tsx index 6477e0ee920..c9849c755d5 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRows.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRows.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataFrame, DataTransformerConfig, standardTransformersRegistry } from '@grafana/data'; import { TransformationOperationRow } from './TransformationOperationRow'; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx index da6ff1462fd..83586953eb1 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { TransformationsEditor } from './TransformationsEditor'; -import { PanelModel } from '../../state'; -import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; +import React from 'react'; + +import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; + +import { PanelModel } from '../../state'; + +import { TransformationsEditor } from './TransformationsEditor'; const setup = (transformations: DataTransformerConfig[] = []) => { const panel = new PanelModel({}); @@ -33,13 +36,13 @@ describe('TransformationsEditor', () => { options: {}, }, ]); - const editors = screen.getAllByLabelText(/^Transformation editor/g); + const editors = screen.getAllByLabelText(/^Transformation editor/); expect(editors).toHaveLength(1); }); }); describe('when Add transformation clicked', () => { - it('renders transformations picker', () => { + it('renders transformations picker', async () => { const buttonLabel = 'Add transformation'; setup([ { @@ -49,7 +52,7 @@ describe('TransformationsEditor', () => { ]); const addTransformationButton = screen.getByText(buttonLabel); - userEvent.click(addTransformationButton); + await userEvent.click(addTransformationButton); const search = screen.getByLabelText(selectors.components.Transforms.searchInput); expect(search).toBeDefined(); @@ -58,7 +61,7 @@ describe('TransformationsEditor', () => { describe('actions', () => { describe('debug', () => { - it('should show/hide debugger', () => { + it('should show/hide debugger', async () => { setup([ { id: 'reduce', @@ -70,7 +73,7 @@ describe('TransformationsEditor', () => { expect(screen.queryByLabelText(debuggerSelector)).toBeNull(); const debugButton = screen.getByLabelText(selectors.components.QueryEditorRow.actionButton('Debug')); - userEvent.click(debugButton); + await userEvent.click(debugButton); expect(screen.getByLabelText(debuggerSelector)).toBeInTheDocument(); }); diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index 5450522ce61..8e9c99230ed 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -1,4 +1,19 @@ +import { css } from '@emotion/css'; import React, { ChangeEvent } from 'react'; +import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; +import { Unsubscribable } from 'rxjs'; + +import { + DataFrame, + DataTransformerConfig, + DocsId, + GrafanaTheme2, + PanelData, + SelectableValue, + standardTransformersRegistry, + TransformerRegistryItem, +} from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Alert, Button, @@ -12,28 +27,16 @@ import { useStyles2, Card, } from '@grafana/ui'; -import { - DataFrame, - DataTransformerConfig, - DocsId, - GrafanaTheme2, - PanelData, - SelectableValue, - standardTransformersRegistry, - TransformerRegistryItem, -} from '@grafana/data'; -import { css } from '@emotion/css'; -import { selectors } from '@grafana/e2e-selectors'; -import { Unsubscribable } from 'rxjs'; -import { PanelModel } from '../../state'; +import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider'; import { getDocsLink } from 'app/core/utils/docsLinks'; -import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; +import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; + +import { AppNotificationSeverity } from '../../../../types'; +import { PanelModel } from '../../state'; +import { PanelNotSupported } from '../PanelEditor/PanelNotSupported'; + import { TransformationOperationRows } from './TransformationOperationRows'; import { TransformationsEditorTransformation } from './types'; -import { PanelNotSupported } from '../PanelEditor/PanelNotSupported'; -import { AppNotificationSeverity } from '../../../../types'; -import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider'; -import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; const LOCAL_STORAGE_KEY = 'dashboard.components.TransformationEditor.featureInfoBox.isDismissed'; diff --git a/public/app/features/dashboard/components/VersionHistory/DiffGroup.tsx b/public/app/features/dashboard/components/VersionHistory/DiffGroup.tsx index cfe61de8e7f..487d43c8d37 100644 --- a/public/app/features/dashboard/components/VersionHistory/DiffGroup.tsx +++ b/public/app/features/dashboard/components/VersionHistory/DiffGroup.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { last } from 'lodash'; -import { useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import { last } from 'lodash'; +import React from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { useStyles } from '@grafana/ui'; + import { DiffTitle } from './DiffTitle'; import { DiffValues } from './DiffValues'; import { Diff, getDiffText } from './utils'; diff --git a/public/app/features/dashboard/components/VersionHistory/DiffTitle.tsx b/public/app/features/dashboard/components/VersionHistory/DiffTitle.tsx index 1637b4b76b0..e62157343d9 100644 --- a/public/app/features/dashboard/components/VersionHistory/DiffTitle.tsx +++ b/public/app/features/dashboard/components/VersionHistory/DiffTitle.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { useStyles, Icon } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; -import { Diff, getDiffText } from './utils'; +import React from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { useStyles, Icon } from '@grafana/ui'; + import { DiffValues } from './DiffValues'; +import { Diff, getDiffText } from './utils'; type DiffTitleProps = { diff?: Diff; diff --git a/public/app/features/dashboard/components/VersionHistory/DiffValues.tsx b/public/app/features/dashboard/components/VersionHistory/DiffValues.tsx index f7e901213f9..c59207bf466 100644 --- a/public/app/features/dashboard/components/VersionHistory/DiffValues.tsx +++ b/public/app/features/dashboard/components/VersionHistory/DiffValues.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { isArray, isObject, isUndefined } from 'lodash'; -import { useStyles2, Icon } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import { isArray, isObject, isUndefined } from 'lodash'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2, Icon } from '@grafana/ui'; + import { Diff } from './utils'; type DiffProps = { diff --git a/public/app/features/dashboard/components/VersionHistory/DiffViewer.tsx b/public/app/features/dashboard/components/VersionHistory/DiffViewer.tsx index fcf0cfc7ca4..52490961500 100644 --- a/public/app/features/dashboard/components/VersionHistory/DiffViewer.tsx +++ b/public/app/features/dashboard/components/VersionHistory/DiffViewer.tsx @@ -1,9 +1,10 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; import ReactDiffViewer, { ReactDiffViewerProps, DiffMethod } from 'react-diff-viewer'; -import { useTheme } from '@grafana/ui'; import tinycolor from 'tinycolor2'; +import { useTheme } from '@grafana/ui'; + export const DiffViewer: React.FC = ({ oldValue, newValue }) => { const theme = useTheme(); diff --git a/public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts b/public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts index fd36f7b2a37..8502085fad7 100644 --- a/public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistorySrv.test.ts @@ -1,7 +1,8 @@ -import { restore, versions } from './__mocks__/dashboardHistoryMocks'; -import { HistorySrv } from './HistorySrv'; import { DashboardModel } from '../../state/DashboardModel'; +import { HistorySrv } from './HistorySrv'; +import { restore, versions } from './__mocks__/dashboardHistoryMocks'; + const getMock = jest.fn().mockResolvedValue({}); const postMock = jest.fn().mockResolvedValue({}); diff --git a/public/app/features/dashboard/components/VersionHistory/HistorySrv.ts b/public/app/features/dashboard/components/VersionHistory/HistorySrv.ts index 5bcd302ffac..0c9db504b01 100644 --- a/public/app/features/dashboard/components/VersionHistory/HistorySrv.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistorySrv.ts @@ -1,7 +1,9 @@ import { isNumber } from 'lodash'; -import { DashboardModel } from '../../state/DashboardModel'; + import { getBackendSrv } from '@grafana/runtime'; +import { DashboardModel } from '../../state/DashboardModel'; + export interface HistoryListOpts { limit: number; start: number; diff --git a/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx b/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx index f4f2641d5e0..0420131e03a 100644 --- a/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx +++ b/public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx @@ -1,5 +1,7 @@ import React, { useEffect } from 'react'; + import { ConfirmModal } from '@grafana/ui'; + import { useDashboardRestore } from './useDashboardRestore'; export interface RevertDashboardModalProps { hideModal: () => void; diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryButtons.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryButtons.tsx index b720d72822b..c8d0d1ad083 100644 --- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryButtons.tsx +++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryButtons.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { HorizontalGroup, Tooltip, Button } from '@grafana/ui'; type VersionsButtonsType = { diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx index e132b20a647..8af4e929f90 100644 --- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx +++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryComparison.tsx @@ -1,13 +1,15 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; +import { GrafanaTheme } from '@grafana/data'; import { Button, ModalsController, CollapsableSection, HorizontalGroup, useStyles } from '@grafana/ui'; + import { DecoratedRevisionModel } from '../DashboardSettings/VersionsSettings'; -import { RevertDashboardModal } from './RevertDashboardModal'; + import { DiffGroup } from './DiffGroup'; import { DiffViewer } from './DiffViewer'; +import { RevertDashboardModal } from './RevertDashboardModal'; import { jsonDiff } from './utils'; -import { GrafanaTheme } from '@grafana/data'; type DiffViewProps = { isNewLatest: boolean; diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryHeader.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryHeader.tsx index 8ad1cd2ccee..c1109172d62 100644 --- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryHeader.tsx +++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryHeader.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { css } from '@emotion/css'; import { noop } from 'lodash'; +import React from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { Icon, useStyles } from '@grafana/ui'; diff --git a/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx b/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx index c0b12528f3c..b1809b4502e 100644 --- a/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx +++ b/public/app/features/dashboard/components/VersionHistory/VersionHistoryTable.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { Checkbox, Button, Tag, ModalsController } from '@grafana/ui'; + import { DecoratedRevisionModel } from '../DashboardSettings/VersionsSettings'; + import { RevertDashboardModal } from './RevertDashboardModal'; type VersionsTableProps = { diff --git a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx index f2422cc850d..b54c2d017fb 100644 --- a/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx +++ b/public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx @@ -1,12 +1,15 @@ import { useEffect } from 'react'; import { useSelector } from 'react-redux'; import { useAsyncFn } from 'react-use'; + import { locationUtil } from '@grafana/data'; -import { StoreState } from 'app/types'; -import { useAppNotification } from 'app/core/copy/appNotification'; -import { historySrv } from './HistorySrv'; -import { DashboardModel } from '../../state'; import { locationService } from '@grafana/runtime'; +import { useAppNotification } from 'app/core/copy/appNotification'; +import { StoreState } from 'app/types'; + +import { DashboardModel } from '../../state'; + +import { historySrv } from './HistorySrv'; const restoreDashboard = async (version: number, dashboard: DashboardModel) => { return await historySrv.restoreDashboard(dashboard, version); diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx index 4c68581de51..c81a9980224 100644 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx @@ -1,23 +1,30 @@ +import { render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; -import { render, screen } from '@testing-library/react'; -import { Props, UnthemedDashboardPage } from './DashboardPage'; -import { Props as LazyLoaderProps } from '../dashgrid/LazyLoader'; import { Router } from 'react-router-dom'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import { DashboardModel } from '../state'; -import { configureStore } from '../../../store/configureStore'; -import { mockToolkitActionCreator } from 'test/core/redux/mocks'; -import { DashboardInitPhase, DashboardRoutes } from 'app/types'; -import { notifyApp } from 'app/core/actions'; -import { selectors } from '@grafana/e2e-selectors'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; -import { createTheme } from '@grafana/data'; +import { useEffectOnce } from 'react-use'; import { AutoSizerProps } from 'react-virtualized-auto-sizer'; +import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + +import { createTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { notifyApp } from 'app/core/actions'; +import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { DashboardInitPhase, DashboardRoutes } from 'app/types'; + +import { configureStore } from '../../../store/configureStore'; +import { Props as LazyLoaderProps } from '../dashgrid/LazyLoader'; import { setDashboardSrv } from '../services/DashboardSrv'; +import { DashboardModel } from '../state'; + +import { Props, UnthemedDashboardPage } from './DashboardPage'; jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => { - const LazyLoader = ({ children }: Pick) => { + const LazyLoader = ({ children, onLoad }: Pick) => { + useEffectOnce(() => { + onLoad?.(); + }); return <>{typeof children === 'function' ? children({ isInView: true }) : children}; }; return { LazyLoader }; diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index c73c0c6fb16..32904dd6042 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -1,35 +1,37 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; +import classnames from 'classnames'; +import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { locationService } from '@grafana/runtime'; + +import { GrafanaTheme2, TimeRange } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { locationService } from '@grafana/runtime'; import { CustomScrollbar, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui'; -import { createErrorNotification } from 'app/core/copy/appNotification'; -import { Branding } from 'app/core/components/Branding/Branding'; -import { DashboardGrid } from '../dashgrid/DashboardGrid'; -import { DashNav } from '../components/DashNav'; -import { DashboardSettings } from '../components/DashboardSettings'; -import { PanelEditor } from '../components/PanelEditor/PanelEditor'; -import { initDashboard } from '../state/initDashboard'; import { notifyApp } from 'app/core/actions'; -import { KioskMode, StoreState } from 'app/types'; +import { Branding } from 'app/core/components/Branding/Branding'; +import { createErrorNotification } from 'app/core/copy/appNotification'; +import { getKioskMode } from 'app/core/navigation/kiosk'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { PanelModel } from 'app/features/dashboard/state'; -import { PanelInspector } from '../components/Inspector/PanelInspector'; -import { SubMenu } from '../components/SubMenu/SubMenu'; -import { cleanUpDashboardAndVariables } from '../state/actions'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { KioskMode, StoreState } from 'app/types'; +import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; + import { cancelVariables, templateVarsChangedInUrl } from '../../variables/state/actions'; import { findTemplateVarChanges } from '../../variables/utils'; -import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { getTimeSrv } from '../services/TimeSrv'; -import { getKioskMode } from 'app/core/navigation/kiosk'; -import { GrafanaTheme2, TimeRange } from '@grafana/data'; -import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading'; +import { DashNav } from '../components/DashNav'; import { DashboardFailed } from '../components/DashboardLoading/DashboardFailed'; +import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading'; import { DashboardPrompt } from '../components/DashboardPrompt/DashboardPrompt'; -import classnames from 'classnames'; -import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; +import { DashboardSettings } from '../components/DashboardSettings'; +import { PanelInspector } from '../components/Inspector/PanelInspector'; +import { PanelEditor } from '../components/PanelEditor/PanelEditor'; +import { SubMenu } from '../components/SubMenu/SubMenu'; +import { DashboardGrid } from '../dashgrid/DashboardGrid'; import { liveTimer } from '../dashgrid/liveTimer'; +import { getTimeSrv } from '../services/TimeSrv'; +import { cleanUpDashboardAndVariables } from '../state/actions'; +import { initDashboard } from '../state/initDashboard'; export interface DashboardPageRouteParams { uid?: string; diff --git a/public/app/features/dashboard/containers/SoloPanelPage.test.tsx b/public/app/features/dashboard/containers/SoloPanelPage.test.tsx index 9ae03c233cb..c292c978f37 100644 --- a/public/app/features/dashboard/containers/SoloPanelPage.test.tsx +++ b/public/app/features/dashboard/containers/SoloPanelPage.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { Props, SoloPanelPage } from './SoloPanelPage'; +import React from 'react'; + +import { DashboardRoutes } from 'app/types'; + +import { getRouteComponentProps } from '../../../core/navigation/__mocks__/routeProps'; import { Props as DashboardPanelProps } from '../dashgrid/DashboardPanel'; import { DashboardModel } from '../state'; -import { DashboardRoutes } from 'app/types'; -import { getRouteComponentProps } from '../../../core/navigation/__mocks__/routeProps'; + +import { Props, SoloPanelPage } from './SoloPanelPage'; jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => ({})); jest.mock('app/features/dashboard/dashgrid/DashboardPanel', () => { diff --git a/public/app/features/dashboard/containers/SoloPanelPage.tsx b/public/app/features/dashboard/containers/SoloPanelPage.tsx index b03acf813bd..2d7360f2cdb 100644 --- a/public/app/features/dashboard/containers/SoloPanelPage.tsx +++ b/public/app/features/dashboard/containers/SoloPanelPage.tsx @@ -1,11 +1,13 @@ import React, { Component } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; + +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; +import { StoreState } from 'app/types'; + import { DashboardPanel } from '../dashgrid/DashboardPanel'; import { initDashboard } from '../state/initDashboard'; -import { StoreState } from 'app/types'; -import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; export interface DashboardPageRouteParams { uid?: string; diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx index e12caba291c..95963ad97d5 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { DashboardGridUnconnected as DashboardGrid, Props } from './DashboardGrid'; +import React from 'react'; + import { DashboardModel } from '../state'; +import { DashboardGridUnconnected as DashboardGrid, Props } from './DashboardGrid'; + jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => { const LazyLoader: React.FC = ({ children }) => { return <>{children}; diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx index 29d6578dc9a..e5d8e613f20 100644 --- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx @@ -1,23 +1,21 @@ -// Libraries +import classNames from 'classnames'; import React, { PureComponent, CSSProperties } from 'react'; import ReactGridLayout, { ItemCallback } from 'react-grid-layout'; -import classNames from 'classnames'; +import { connect, ConnectedProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; +import { Subscription } from 'rxjs'; + +import { config } from '@grafana/runtime'; +import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; +import { cleanAndRemoveMany } from 'app/features/panel/state/actions'; +import { DashboardPanelsChangedEvent } from 'app/types/events'; -// Components import { AddPanelWidget } from '../components/AddPanelWidget'; import { DashboardRow } from '../components/DashboardRow'; - -// Types -import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; -import { DashboardPanel } from './DashboardPanel'; import { DashboardModel, PanelModel } from '../state'; -import { Subscription } from 'rxjs'; -import { connect, ConnectedProps } from 'react-redux'; -import { DashboardPanelsChangedEvent } from 'app/types/events'; -import { cleanAndRemoveMany } from 'app/features/panel/state/actions'; import { GridPos } from '../state/PanelModel'; -import { config } from '@grafana/runtime'; + +import { DashboardPanel } from './DashboardPanel'; export interface OwnProps { dashboard: DashboardModel; diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 0401ecf9397..773e6de1709 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,13 +1,15 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + +import { StoreState } from 'app/types'; + +import { initPanelState } from '../../panel/state/actions'; +import { setPanelInstanceState } from '../../panel/state/reducers'; +import { DashboardModel, PanelModel } from '../state'; + +import { LazyLoader } from './LazyLoader'; import { PanelChrome } from './PanelChrome'; import { PanelChromeAngular } from './PanelChromeAngular'; -import { DashboardModel, PanelModel } from '../state'; -import { StoreState } from 'app/types'; -import { PanelPlugin } from '@grafana/data'; -import { setPanelInstanceState } from '../../panel/state/reducers'; -import { initPanelState } from '../../panel/state/actions'; -import { LazyLoader } from './LazyLoader'; export interface OwnProps { panel: PanelModel; @@ -48,8 +50,8 @@ export class DashboardPanelUnconnected extends PureComponent { componentDidMount() { this.props.panel.isInView = !this.props.lazy; - if (!this.props.plugin) { - this.props.initPanelState(this.props.panel); + if (!this.props.lazy) { + this.onPanelLoad(); } } @@ -61,11 +63,18 @@ export class DashboardPanelUnconnected extends PureComponent { this.props.panel.isInView = v; }; - renderPanel(plugin: PanelPlugin) { - const { dashboard, panel, isViewing, isEditing, width, height, lazy } = this.props; + onPanelLoad = () => { + if (!this.props.plugin) { + this.props.initPanelState(this.props.panel); + } + }; + + render() { + const { dashboard, panel, isViewing, isEditing, width, height, lazy, plugin } = this.props; const renderPanelChrome = (isInView: boolean) => - plugin.angularPanelCtrl ? ( + plugin && + (plugin.angularPanelCtrl ? ( { height={height} onInstanceStateChange={this.onInstanceStateChange} /> - ); + )); return lazy ? ( - + {({ isInView }) => renderPanelChrome(isInView)} ) : ( renderPanelChrome(true) ); } - - render() { - const { plugin } = this.props; - - // If we have not loaded plugin exports yet, wait - if (!plugin) { - return null; - } - - return this.renderPanel(plugin); - } } export const DashboardPanel = connector(DashboardPanelUnconnected); diff --git a/public/app/features/dashboard/dashgrid/LazyLoader.tsx b/public/app/features/dashboard/dashgrid/LazyLoader.tsx index 30f9034a125..da9339e5459 100644 --- a/public/app/features/dashboard/dashgrid/LazyLoader.tsx +++ b/public/app/features/dashboard/dashgrid/LazyLoader.tsx @@ -1,7 +1,8 @@ import React, { useRef, useState } from 'react'; -import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; import { useEffectOnce } from 'react-use'; +import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; + export interface Props { children: React.ReactNode | (({ isInView }: { isInView: boolean }) => React.ReactNode); width?: number; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.test.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.test.tsx index 148c3628c8a..a883596bac4 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.test.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.test.tsx @@ -1,14 +1,16 @@ +import { act, render, screen } from '@testing-library/react'; import React, { FC } from 'react'; -import { ReplaySubject } from 'rxjs'; import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; -import { act, render, screen } from '@testing-library/react'; +import { ReplaySubject } from 'rxjs'; + import { EventBusSrv, getDefaultTimeRange, LoadingState, PanelData, PanelPlugin, PanelProps } from '@grafana/data'; -import { PanelChrome, Props } from './PanelChrome'; -import { DashboardModel, PanelModel } from '../state'; import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; import { setTimeSrv, TimeSrv } from '../services/TimeSrv'; +import { DashboardModel, PanelModel } from '../state'; + +import { PanelChrome, Props } from './PanelChrome'; jest.mock('app/core/profiler', () => ({ profiler: { diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 890c6c86075..7ace1ab35fa 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -1,7 +1,7 @@ -import React, { PureComponent } from 'react'; import classNames from 'classnames'; +import React, { PureComponent } from 'react'; import { Subscription } from 'rxjs'; -import { locationService, RefreshEvent } from '@grafana/runtime'; + import { AbsoluteTimeRange, AnnotationChangeEvent, @@ -19,26 +19,28 @@ import { toDataFrameDTO, toUtc, } from '@grafana/data'; -import { ErrorBoundary, PanelContext, PanelContextProvider, SeriesVisibilityChangeMode } from '@grafana/ui'; -import { VizLegendOptions } from '@grafana/schema'; import { selectors } from '@grafana/e2e-selectors'; - -import { PanelHeader } from './PanelHeader/PanelHeader'; -import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; -import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; -import { profiler } from 'app/core/profiler'; +import { locationService, RefreshEvent } from '@grafana/runtime'; +import { VizLegendOptions } from '@grafana/schema'; +import { ErrorBoundary, PanelContext, PanelContextProvider, SeriesVisibilityChangeMode } from '@grafana/ui'; import config from 'app/core/config'; -import { DashboardModel, PanelModel } from '../state'; import { PANEL_BORDER } from 'app/core/constants'; -import { loadSnapshotData } from '../utils/loadSnapshotData'; -import { RenderEvent } from 'app/types/events'; +import { profiler } from 'app/core/profiler'; +import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; import { changeSeriesColorConfigFactory } from 'app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory'; -import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory'; +import { RenderEvent } from 'app/types/events'; + +import { contextSrv } from '../../../core/services/context_srv'; +import { isSoloRoute } from '../../../routes/utils'; import { deleteAnnotation, saveAnnotation, updateAnnotation } from '../../annotations/api'; import { getDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; +import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; +import { DashboardModel, PanelModel } from '../state'; +import { loadSnapshotData } from '../utils/loadSnapshotData'; + +import { PanelHeader } from './PanelHeader/PanelHeader'; +import { seriesVisibilityConfigFactory } from './SeriesVisibilityConfigFactory'; import { liveTimer } from './liveTimer'; -import { isSoloRoute } from '../../../routes/utils'; -import { contextSrv } from '../../../core/services/context_srv'; const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; diff --git a/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx b/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx index 3d82607222b..7f967d603b1 100644 --- a/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChromeAngular.tsx @@ -1,20 +1,22 @@ -import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import { Subscription } from 'rxjs'; +import React, { PureComponent } from 'react'; import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { AngularComponent, getAngularLoader, locationService } from '@grafana/runtime'; +import { Subscription } from 'rxjs'; + import { getDefaultTimeRange, LoadingState, PanelData, PanelPlugin } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { AngularComponent, getAngularLoader, locationService } from '@grafana/runtime'; +import config from 'app/core/config'; +import { PANEL_BORDER } from 'app/core/constants'; +import { setPanelAngularComponent } from 'app/features/panel/state/reducers'; +import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { StoreState } from 'app/types'; + +import { isSoloRoute } from '../../../routes/utils'; +import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; +import { DashboardModel, PanelModel } from '../state'; import { PanelHeader } from './PanelHeader/PanelHeader'; -import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; -import { setPanelAngularComponent } from 'app/features/panel/state/reducers'; -import config from 'app/core/config'; -import { DashboardModel, PanelModel } from '../state'; -import { StoreState } from 'app/types'; -import { PANEL_BORDER } from 'app/core/constants'; -import { isSoloRoute } from '../../../routes/utils'; -import { getPanelStateForModel } from 'app/features/panel/state/selectors'; interface OwnProps { panel: PanelModel; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index a5f020a89a6..36c377f0d37 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -1,17 +1,18 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; -import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data'; -import { Icon, useStyles2 } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; +import React, { FC } from 'react'; -import PanelHeaderCorner from './PanelHeaderCorner'; +import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Icon, useStyles2 } from '@grafana/ui'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; -import { PanelHeaderNotices } from './PanelHeaderNotices'; -import { PanelHeaderMenuTrigger } from './PanelHeaderMenuTrigger'; + +import PanelHeaderCorner from './PanelHeaderCorner'; import { PanelHeaderLoadingIndicator } from './PanelHeaderLoadingIndicator'; +import { PanelHeaderMenuTrigger } from './PanelHeaderMenuTrigger'; import { PanelHeaderMenuWrapper } from './PanelHeaderMenuWrapper'; +import { PanelHeaderNotices } from './PanelHeaderNotices'; export interface Props { panel: PanelModel; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.test.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.test.tsx index 9d2095e2572..20a88cff82d 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.test.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { PanelHeaderCorner } from './PanelHeaderCorner'; +import React from 'react'; + import { PanelModel } from '../../state'; +import { PanelHeaderCorner } from './PanelHeaderCorner'; + describe('Render', () => { it('should render component', () => { const panel = new PanelModel({}); diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 0448a550a08..09b5b83cfd1 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -1,13 +1,12 @@ import React, { Component } from 'react'; import { renderMarkdown, LinkModelSupplier, ScopedVars } from '@grafana/data'; -import { Tooltip, PopoverContent } from '@grafana/ui'; -import { locationService, getTemplateSrv } from '@grafana/runtime'; - -import { PanelModel } from 'app/features/dashboard/state/PanelModel'; -import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { InspectTab } from 'app/features/inspector/types'; import { selectors } from '@grafana/e2e-selectors'; +import { locationService, getTemplateSrv } from '@grafana/runtime'; +import { Tooltip, PopoverContent } from '@grafana/ui'; +import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { PanelModel } from 'app/features/dashboard/state/PanelModel'; +import { InspectTab } from 'app/features/inspector/types'; enum InfoMode { Error = 'Error', diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderLoadingIndicator.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderLoadingIndicator.tsx index fb1d42a596a..14151ba1417 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderLoadingIndicator.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderLoadingIndicator.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme, LoadingState } from '@grafana/data'; import { Icon, Tooltip, useStyles } from '@grafana/ui'; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index c180359922a..af37c1b6dbc 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -1,7 +1,9 @@ import React, { PureComponent } from 'react'; -import { PanelHeaderMenuItem } from './PanelHeaderMenuItem'; + import { PanelMenuItem } from '@grafana/data'; +import { PanelHeaderMenuItem } from './PanelHeaderMenuItem'; + export interface Props { items: PanelMenuItem[]; } diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx index ab1a5395583..2e25ccab6e9 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx @@ -1,8 +1,9 @@ -import React, { FC, useState } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useState } from 'react'; + import { PanelMenuItem } from '@grafana/data'; -import { Icon, IconName, useTheme } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; +import { Icon, IconName, useTheme } from '@grafana/ui'; interface Props { children?: any; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx index b3b2afe1ab2..54088922bf1 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx @@ -1,12 +1,13 @@ import { FC, ReactElement, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { PanelMenuItem } from '@grafana/data'; -import { DashboardModel, PanelModel } from '../../state'; -import { StoreState } from '../../../../types'; -import { getPanelMenu } from '../../utils/getPanelMenu'; +import { PanelMenuItem } from '@grafana/data'; import { getPanelStateForModel } from 'app/features/panel/state/selectors'; +import { StoreState } from '../../../../types'; +import { DashboardModel, PanelModel } from '../../state'; +import { getPanelMenu } from '../../utils/getPanelMenu'; + interface PanelHeaderMenuProviderApi { items: PanelMenuItem[]; } diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuTrigger.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuTrigger.tsx index 509dd0e0c1e..174c07a2d49 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuTrigger.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuTrigger.tsx @@ -1,4 +1,5 @@ import React, { FC, HTMLAttributes, MouseEvent, ReactElement, useCallback, useState } from 'react'; + import { CartesianCoords2D } from '@grafana/data'; interface PanelHeaderMenuTriggerApi { diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuWrapper.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuWrapper.tsx index 596dc96fa65..40d69a5efe7 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuWrapper.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuWrapper.tsx @@ -1,9 +1,12 @@ import React, { FC } from 'react'; + import { ClickOutsideWrapper } from '@grafana/ui'; -import { PanelHeaderMenuProvider } from './PanelHeaderMenuProvider'; -import { PanelHeaderMenu } from './PanelHeaderMenu'; + import { DashboardModel, PanelModel } from '../../state'; +import { PanelHeaderMenu } from './PanelHeaderMenu'; +import { PanelHeaderMenuProvider } from './PanelHeaderMenuProvider'; + interface Props { panel: PanelModel; dashboard: DashboardModel; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotice.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotice.tsx index 16e9670523a..18e13ca1ccc 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotice.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotice.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { QueryResultMetaNotice } from '@grafana/data'; import { Icon, Tooltip } from '@grafana/ui'; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotices.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotices.tsx index 4e70fdad7e9..76b98043de7 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotices.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderNotices.tsx @@ -1,8 +1,10 @@ import React, { FC, useCallback } from 'react'; + import { DataFrame, QueryResultMetaNotice } from '@grafana/data'; -import { PanelHeaderNotice } from './PanelHeaderNotice'; import { locationService } from '@grafana/runtime'; +import { PanelHeaderNotice } from './PanelHeaderNotice'; + interface Props { panelId: number; frames: DataFrame[]; diff --git a/public/app/features/dashboard/dashgrid/PanelResizer.tsx b/public/app/features/dashboard/dashgrid/PanelResizer.tsx index 3bd191300ff..f7dd6757dac 100644 --- a/public/app/features/dashboard/dashgrid/PanelResizer.tsx +++ b/public/app/features/dashboard/dashgrid/PanelResizer.tsx @@ -1,5 +1,5 @@ -import React, { PureComponent } from 'react'; import { throttle } from 'lodash'; +import React, { PureComponent } from 'react'; import Draggable, { DraggableEventHandler } from 'react-draggable'; import { PanelModel } from '../state/PanelModel'; diff --git a/public/app/features/dashboard/dashgrid/liveTimer.ts b/public/app/features/dashboard/dashgrid/liveTimer.ts index 0631c3ca4db..65b37811ea3 100644 --- a/public/app/features/dashboard/dashgrid/liveTimer.ts +++ b/public/app/features/dashboard/dashgrid/liveTimer.ts @@ -1,5 +1,7 @@ -import { dateMath, dateTime, TimeRange } from '@grafana/data'; import { BehaviorSubject } from 'rxjs'; + +import { dateMath, dateTime, TimeRange } from '@grafana/data'; + import { PanelChrome } from './PanelChrome'; // target is 20hz (50ms), but we poll at 100ms to smooth out jitter diff --git a/public/app/features/dashboard/services/DashboardLoaderSrv.ts b/public/app/features/dashboard/services/DashboardLoaderSrv.ts index dc06ab6e1d2..68a4ec18e6f 100644 --- a/public/app/features/dashboard/services/DashboardLoaderSrv.ts +++ b/public/app/features/dashboard/services/DashboardLoaderSrv.ts @@ -1,16 +1,18 @@ -import moment from 'moment'; // eslint-disable-line no-restricted-imports -// eslint-disable-next-line lodash/import-scope -import _, { isFunction } from 'lodash'; import $ from 'jquery'; -import kbn from 'app/core/utils/kbn'; +import _, { isFunction } from 'lodash'; // eslint-disable-line lodash/import-scope +import moment from 'moment'; // eslint-disable-line no-restricted-imports + import { AppEvents, dateMath, UrlQueryValue } from '@grafana/data'; -import impressionSrv from 'app/core/services/impression_srv'; -import { backendSrv } from 'app/core/services/backend_srv'; -import { getDashboardSrv } from './DashboardSrv'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getBackendSrv, locationService } from '@grafana/runtime'; +import { backendSrv } from 'app/core/services/backend_srv'; +import impressionSrv from 'app/core/services/impression_srv'; +import kbn from 'app/core/utils/kbn'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; + import { appEvents } from '../../../core/core'; +import { getDashboardSrv } from './DashboardSrv'; + export class DashboardLoaderSrv { constructor() {} _dashboardLoadFailed(title: string, snapshot?: boolean) { diff --git a/public/app/features/dashboard/services/DashboardSrv.ts b/public/app/features/dashboard/services/DashboardSrv.ts index 7fa5084d850..0a0ae013172 100644 --- a/public/app/features/dashboard/services/DashboardSrv.ts +++ b/public/app/features/dashboard/services/DashboardSrv.ts @@ -1,12 +1,14 @@ +import { lastValueFrom } from 'rxjs'; + +import { BackendSrvRequest } from '@grafana/runtime'; import { appEvents } from 'app/core/app_events'; -import { DashboardModel } from '../state/DashboardModel'; -import { removePanel } from '../utils/panel'; -import { DashboardMeta } from 'app/types'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { saveDashboard } from 'app/features/manage-dashboards/state/actions'; +import { DashboardMeta } from 'app/types'; + import { RemovePanelEvent } from '../../../types/events'; -import { BackendSrvRequest } from '@grafana/runtime'; -import { lastValueFrom } from 'rxjs'; +import { DashboardModel } from '../state/DashboardModel'; +import { removePanel } from '../utils/panel'; export interface SaveDashboardOptions { /** The complete dashboard model. If `dashboard.id` is not set a new dashboard will be created. */ diff --git a/public/app/features/dashboard/services/TimeSrv.test.ts b/public/app/features/dashboard/services/TimeSrv.test.ts index 90220dd63c1..26124b7b299 100644 --- a/public/app/features/dashboard/services/TimeSrv.test.ts +++ b/public/app/features/dashboard/services/TimeSrv.test.ts @@ -1,10 +1,13 @@ -import { TimeSrv } from './TimeSrv'; -import { ContextSrvStub } from 'test/specs/helpers'; -import { dateTime, isDateTime } from '@grafana/data'; import * as H from 'history'; +import { ContextSrvStub } from 'test/specs/helpers'; + +import { dateTime, isDateTime } from '@grafana/data'; import { HistoryWrapper, locationService, setLocationService } from '@grafana/runtime'; + import { beforeEach } from '../../../../test/lib/common'; +import { TimeSrv } from './TimeSrv'; + jest.mock('app/core/core', () => ({ appEvents: { subscribe: () => {}, diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts index 490f480ce55..7966a83387b 100644 --- a/public/app/features/dashboard/services/TimeSrv.ts +++ b/public/app/features/dashboard/services/TimeSrv.ts @@ -1,4 +1,5 @@ import { cloneDeep, extend, isString } from 'lodash'; + import { dateMath, dateTime, @@ -9,14 +10,15 @@ import { TimeRange, toUtc, } from '@grafana/data'; -import { getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker'; -import { config } from 'app/core/config'; -import { getRefreshFromUrl } from '../utils/getRefreshFromUrl'; import { locationService } from '@grafana/runtime'; -import { AbsoluteTimeEvent, ShiftTimeEvent, ShiftTimeEventDirection, ZoomOutEvent } from '../../../types/events'; -import { contextSrv, ContextSrv } from 'app/core/services/context_srv'; import appEvents from 'app/core/app_events'; +import { config } from 'app/core/config'; +import { contextSrv, ContextSrv } from 'app/core/services/context_srv'; +import { getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker'; + +import { AbsoluteTimeEvent, ShiftTimeEvent, ShiftTimeEventDirection, ZoomOutEvent } from '../../../types/events'; import { TimeModel } from '../state/TimeModel'; +import { getRefreshFromUrl } from '../utils/getRefreshFromUrl'; export class TimeSrv { time: any; diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index 201ba1ce202..b53a4088208 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -1,15 +1,17 @@ import { each, map } from 'lodash'; +import { expect } from 'test/lib/common'; + +import { DataLinkBuiltInVars, MappingType } from '@grafana/data'; +import { setDataSourceSrv } from '@grafana/runtime'; +import { config } from 'app/core/config'; +import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; +import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; +import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; +import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; + +import { VariableHide } from '../../variables/types'; import { DashboardModel } from '../state/DashboardModel'; import { PanelModel } from '../state/PanelModel'; -import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants'; -import { expect } from 'test/lib/common'; -import { DataLinkBuiltInVars, MappingType } from '@grafana/data'; -import { VariableHide } from '../../variables/types'; -import { config } from 'app/core/config'; -import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; -import { setDataSourceSrv } from '@grafana/runtime'; -import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; -import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; jest.mock('app/core/services/context_srv', () => ({})); diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 7ee735ff5d4..f9e5be516c0 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -1,11 +1,5 @@ -// Libraries import { each, find, findIndex, flattenDeep, isArray, isBoolean, isNumber, isString, map, max, some } from 'lodash'; -// Utils -import getFactors from 'app/core/utils/factors'; -import kbn from 'app/core/utils/kbn'; -// Types -import { PanelModel } from './PanelModel'; -import { DashboardModel } from './DashboardModel'; + import { AnnotationQuery, DataLink, @@ -29,7 +23,10 @@ import { ValueMap, ValueMapping, } from '@grafana/data'; -// Constants +import { getDataSourceSrv, setDataSourceSrv } from '@grafana/runtime'; +import { AxisPlacement, GraphFieldConfig } from '@grafana/ui'; +import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/editors/registry'; +import { config } from 'app/core/config'; import { DEFAULT_PANEL_SPAN, DEFAULT_ROW_HEIGHT, @@ -38,24 +35,26 @@ import { GRID_COLUMN_COUNT, MIN_PANEL_HEIGHT, } from 'app/core/constants'; +import getFactors from 'app/core/utils/factors'; +import kbn from 'app/core/utils/kbn'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { isConstant, isMulti } from 'app/features/variables/guard'; import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions'; -import { VariableHide } from '../../variables/types'; -import { config } from 'app/core/config'; -import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module'; -import { plugin as gaugePanelPlugin } from 'app/plugins/panel/gauge/module'; -import { AxisPlacement, GraphFieldConfig } from '@grafana/ui'; -import { getDataSourceSrv, setDataSourceSrv } from '@grafana/runtime'; -import { labelsToFieldsTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/labelsToFields'; -import { mergeTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/merge'; import { migrateCloudWatchQuery, migrateMultipleStatsAnnotationQuery, migrateMultipleStatsMetricsQuery, } from 'app/plugins/datasource/cloudwatch/migrations'; import { CloudWatchAnnotationQuery, CloudWatchMetricsQuery } from 'app/plugins/datasource/cloudwatch/types'; -import { getAllOptionEditors, getAllStandardFieldConfigs } from 'app/core/components/editors/registry'; -import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { plugin as gaugePanelPlugin } from 'app/plugins/panel/gauge/module'; +import { plugin as statPanelPlugin } from 'app/plugins/panel/stat/module'; + +import { labelsToFieldsTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/labelsToFields'; +import { mergeTransformer } from '../../../../../packages/grafana-data/src/transformations/transformers/merge'; +import { VariableHide } from '../../variables/types'; + +import { DashboardModel } from './DashboardModel'; +import { PanelModel } from './PanelModel'; standardEditorsRegistry.setInit(getAllOptionEditors); standardFieldConfigEditorRegistry.setInit(getAllStandardFieldConfigs); diff --git a/public/app/features/dashboard/state/DashboardModel.refresh.test.ts b/public/app/features/dashboard/state/DashboardModel.refresh.test.ts index 338540becdc..8eed7928506 100644 --- a/public/app/features/dashboard/state/DashboardModel.refresh.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.refresh.test.ts @@ -1,9 +1,10 @@ -import { DashboardModel } from './DashboardModel'; +import { afterEach, beforeEach } from '../../../../test/lib/common'; import { appEvents } from '../../../core/core'; import { VariablesChanged } from '../../variables/types'; -import { PanelModel } from './PanelModel'; import { getTimeSrv, setTimeSrv } from '../services/TimeSrv'; -import { afterEach, beforeEach } from '../../../../test/lib/common'; + +import { DashboardModel } from './DashboardModel'; +import { PanelModel } from './PanelModel'; function getTestContext({ usePanelInEdit, diff --git a/public/app/features/dashboard/state/DashboardModel.repeat.test.ts b/public/app/features/dashboard/state/DashboardModel.repeat.test.ts index e393b98d8eb..b333eb878a4 100644 --- a/public/app/features/dashboard/state/DashboardModel.repeat.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.repeat.test.ts @@ -1,10 +1,13 @@ import { compact, flattenDeep, map, uniq } from 'lodash'; -import { DashboardModel } from '../state/DashboardModel'; import { expect } from 'test/lib/common'; -import { getDashboardModel } from '../../../../test/helpers/getDashboardModel'; -import { PanelModel } from './PanelModel'; + import { DashboardPanelsChangedEvent } from 'app/types/events'; +import { getDashboardModel } from '../../../../test/helpers/getDashboardModel'; +import { DashboardModel } from '../state/DashboardModel'; + +import { PanelModel } from './PanelModel'; + jest.mock('app/core/services/context_srv', () => ({})); describe('given dashboard with panel repeat', () => { diff --git a/public/app/features/dashboard/state/DashboardModel.test.ts b/public/app/features/dashboard/state/DashboardModel.test.ts index 764cccd8fcd..f5d494a780c 100644 --- a/public/app/features/dashboard/state/DashboardModel.test.ts +++ b/public/app/features/dashboard/state/DashboardModel.test.ts @@ -1,13 +1,14 @@ import { keys as _keys } from 'lodash'; -import { DashboardModel } from '../state/DashboardModel'; -import { PanelModel } from '../state/PanelModel'; + import { getDashboardModel } from '../../../../test/helpers/getDashboardModel'; +import { expect } from '../../../../test/lib/common'; import { variableAdapters } from '../../variables/adapters'; import { createAdHocVariableAdapter } from '../../variables/adhoc/adapter'; -import { createQueryVariableAdapter } from '../../variables/query/adapter'; import { createCustomVariableAdapter } from '../../variables/custom/adapter'; -import { expect } from '../../../../test/lib/common'; +import { createQueryVariableAdapter } from '../../variables/query/adapter'; import { setTimeSrv, TimeSrv } from '../services/TimeSrv'; +import { DashboardModel } from '../state/DashboardModel'; +import { PanelModel } from '../state/PanelModel'; jest.mock('app/core/services/context_srv', () => ({})); diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 874127c04d0..1c99bb5a33a 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -1,4 +1,3 @@ -// Libaries import { cloneDeep, defaults as _defaults, @@ -13,15 +12,8 @@ import { pull, some, } from 'lodash'; -// Constants -import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; -import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL } from 'app/core/constants'; -// Utils & Services -import { contextSrv } from 'app/core/services/context_srv'; -// Types -import { GridPos, PanelModel } from './PanelModel'; -import { TimeModel } from './TimeModel'; -import { DashboardMigrator } from './DashboardMigrator'; +import { Subscription } from 'rxjs'; + import { AnnotationQuery, AppEvent, @@ -37,26 +29,33 @@ import { TimeZone, UrlQueryValue, } from '@grafana/data'; -import { CoreEvents, DashboardMeta, KioskMode } from 'app/types'; -import { GetVariables, getVariablesByKey } from 'app/features/variables/state/selectors'; +import { RefreshEvent, TimeRangeUpdatedEvent } from '@grafana/runtime'; +import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; +import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL } from 'app/core/constants'; +import { contextSrv } from 'app/core/services/context_srv'; +import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; import { variableAdapters } from 'app/features/variables/adapters'; import { onTimeRangeUpdated } from 'app/features/variables/state/actions'; -import { dispatch } from '../../../store/store'; -import { isAllVariable } from '../../variables/utils'; +import { GetVariables, getVariablesByKey } from 'app/features/variables/state/selectors'; +import { CoreEvents, DashboardMeta, KioskMode } from 'app/types'; import { DashboardPanelsChangedEvent, RenderEvent } from 'app/types/events'; -import { getTimeSrv } from '../services/TimeSrv'; -import { mergePanels, PanelMergeInfo } from '../utils/panelMerge'; -import { deleteScopeVars, isOnTheSameGridRow } from './utils'; -import { RefreshEvent, TimeRangeUpdatedEvent } from '@grafana/runtime'; -import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; -import { Subscription } from 'rxjs'; + import { appEvents } from '../../../core/core'; +import { dispatch } from '../../../store/store'; import { VariablesChanged, VariablesChangedEvent, VariablesChangedInUrl, VariablesTimeRangeProcessDone, } from '../../variables/types'; +import { isAllVariable } from '../../variables/utils'; +import { getTimeSrv } from '../services/TimeSrv'; +import { mergePanels, PanelMergeInfo } from '../utils/panelMerge'; + +import { DashboardMigrator } from './DashboardMigrator'; +import { GridPos, PanelModel } from './PanelModel'; +import { TimeModel } from './TimeModel'; +import { deleteScopeVars, isOnTheSameGridRow } from './utils'; export interface CloneOptions { saveVariables?: boolean; @@ -1198,7 +1197,7 @@ export class DashboardModel implements TimeModel { canEditAnnotations(dashboardId: number) { let canEdit = true; - // if FGAC is enabled there are additional conditions to check + // if RBAC is enabled there are additional conditions to check if (contextSrv.accessControlEnabled()) { if (dashboardId === 0) { canEdit = !!this.meta.annotationsPermissions?.organization.canEdit; @@ -1212,7 +1211,7 @@ export class DashboardModel implements TimeModel { canAddAnnotations() { let canAdd = true; - // if FGAC is enabled there are additional conditions to check + // if RBAC is enabled there are additional conditions to check if (contextSrv.accessControlEnabled()) { canAdd = !!this.meta.annotationsPermissions?.dashboard.canAdd; } diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 1af302c5538..df5459411aa 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -1,5 +1,5 @@ -import { PanelModel } from './PanelModel'; -import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; +import { ComponentClass } from 'react'; + import { DataLinkBuiltInVars, FieldConfigProperty, @@ -10,17 +10,20 @@ import { dateTime, TimeRange, } from '@grafana/data'; -import { ComponentClass } from 'react'; -import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; -import { setTimeSrv } from '../services/TimeSrv'; -import { TemplateSrv } from '../../templating/template_srv'; import { setTemplateSrv } from '@grafana/runtime'; +import { queryBuilder } from 'app/features/variables/shared/testing/builders'; + +import { mockStandardFieldConfigOptions } from '../../../../test/helpers/fieldConfig'; +import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; +import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; +import { TemplateSrv } from '../../templating/template_srv'; import { variableAdapters } from '../../variables/adapters'; import { createQueryVariableAdapter } from '../../variables/query/adapter'; -import { mockStandardFieldConfigOptions } from '../../../../test/helpers/fieldConfig'; -import { queryBuilder } from 'app/features/variables/shared/testing/builders'; +import { setTimeSrv } from '../services/TimeSrv'; import { TimeOverrideResult } from '../utils/panel'; +import { PanelModel } from './PanelModel'; + standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions()); standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions()); diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 115c7ed8502..81723a24d62 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -1,10 +1,6 @@ -// Libraries import { cloneDeep, defaultsDeep, isArray, isEqual, keys } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -// Utils -import { getTemplateSrv, RefreshEvent } from '@grafana/runtime'; -import { getNextRefIdChar } from 'app/core/utils/query'; -// Types + import { DataConfigSource, DataFrameDTO, @@ -21,24 +17,29 @@ import { PanelModel as IPanelModel, DataSourceRef, } from '@grafana/data'; +import { getTemplateSrv, RefreshEvent } from '@grafana/runtime'; import config from 'app/core/config'; -import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; +import { getNextRefIdChar } from 'app/core/utils/query'; +import { QueryGroupOptions } from 'app/types'; import { PanelOptionsChangedEvent, PanelQueriesChangedEvent, PanelTransformationsChangedEvent, RenderEvent, } from 'app/types/events'; -import { getTimeSrv } from '../services/TimeSrv'; + +import { PanelModelLibraryPanel } from '../../library-panels/types'; +import { PanelQueryRunner } from '../../query/state/PanelQueryRunner'; import { getVariablesUrlParams } from '../../variables/getAllVariableValuesForUrl'; +import { getTimeSrv } from '../services/TimeSrv'; +import { TimeOverrideResult } from '../utils/panel'; + import { filterFieldConfigOverrides, getPanelOptionsWithDefaults, isStandardFieldProp, restoreCustomOverrideRules, } from './getPanelOptionsWithDefaults'; -import { QueryGroupOptions } from 'app/types'; -import { PanelModelLibraryPanel } from '../../library-panels/types'; export interface GridPos { x: number; @@ -48,8 +49,6 @@ export interface GridPos { static?: boolean; } -import { TimeOverrideResult } from '../utils/panel'; - const notPersistedProperties: { [str: string]: boolean } = { events: true, isViewing: true, diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts index e6c070c2c1b..d31b6e168cc 100644 --- a/public/app/features/dashboard/state/actions.ts +++ b/public/app/features/dashboard/state/actions.ts @@ -1,16 +1,15 @@ -// Services & Utils +import { TimeZone } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import { createSuccessNotification } from 'app/core/copy/appNotification'; -// Actions -import { loadPluginDashboards } from '../../plugins/admin/state/actions'; -import { cleanUpDashboard, loadDashboardPermissions } from './reducers'; import { notifyApp } from 'app/core/actions'; +import { createSuccessNotification } from 'app/core/copy/appNotification'; import { updateTimeZoneForSession, updateWeekStartForSession } from 'app/features/profile/state/reducers'; -// Types import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel, ThunkResult } from 'app/types'; + +import { loadPluginDashboards } from '../../plugins/admin/state/actions'; import { cancelVariables } from '../../variables/state/actions'; import { getTimeSrv } from '../services/TimeSrv'; -import { TimeZone } from '@grafana/data'; + +import { cleanUpDashboard, loadDashboardPermissions } from './reducers'; export function getDashboardPermissions(id: number): ThunkResult { return async (dispatch) => { diff --git a/public/app/features/dashboard/state/analyticsProcessor.ts b/public/app/features/dashboard/state/analyticsProcessor.ts index 9834d86f149..258cf71ee45 100644 --- a/public/app/features/dashboard/state/analyticsProcessor.ts +++ b/public/app/features/dashboard/state/analyticsProcessor.ts @@ -1,6 +1,7 @@ -import { DashboardModel } from './DashboardModel'; import { reportMetaAnalytics, MetaAnalyticsEventName, DashboardViewEventPayload } from '@grafana/runtime'; +import { DashboardModel } from './DashboardModel'; + export function emitDashboardViewEvent(dashboard: DashboardModel) { const eventData: DashboardViewEventPayload = { dashboardId: dashboard.id, diff --git a/public/app/features/dashboard/state/getPanelOptionsWithDefaults.test.ts b/public/app/features/dashboard/state/getPanelOptionsWithDefaults.test.ts index 6b61bdcaf60..a56c2669238 100644 --- a/public/app/features/dashboard/state/getPanelOptionsWithDefaults.test.ts +++ b/public/app/features/dashboard/state/getPanelOptionsWithDefaults.test.ts @@ -1,3 +1,5 @@ +import { mockStandardFieldConfigOptions } from 'test/helpers/fieldConfig'; + import { ConfigOverrideRule, FieldColorModeId, @@ -11,7 +13,7 @@ import { ThresholdsMode, } from '@grafana/data'; import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; -import { mockStandardFieldConfigOptions } from 'test/helpers/fieldConfig'; + import { getPanelOptionsWithDefaults, restoreCustomOverrideRules } from './getPanelOptionsWithDefaults'; standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions()); diff --git a/public/app/features/dashboard/state/getPanelOptionsWithDefaults.ts b/public/app/features/dashboard/state/getPanelOptionsWithDefaults.ts index 2eb30b6d951..fadb6071164 100644 --- a/public/app/features/dashboard/state/getPanelOptionsWithDefaults.ts +++ b/public/app/features/dashboard/state/getPanelOptionsWithDefaults.ts @@ -1,3 +1,5 @@ +import { mergeWith, isArray, isObject, unset, isEqual } from 'lodash'; + import { ConfigOverrideRule, DynamicConfigValue, @@ -11,7 +13,6 @@ import { ThresholdsConfig, ThresholdsMode, } from '@grafana/data'; -import { mergeWith, isArray, isObject, unset, isEqual } from 'lodash'; export interface Props { plugin: PanelPlugin; diff --git a/public/app/features/dashboard/state/initDashboard.test.ts b/public/app/features/dashboard/state/initDashboard.test.ts index 0bcb74ef8d2..eae72482c81 100644 --- a/public/app/features/dashboard/state/initDashboard.test.ts +++ b/public/app/features/dashboard/state/initDashboard.test.ts @@ -1,28 +1,30 @@ -import { Subject } from 'rxjs'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { locationService, setEchoSrv } from '@grafana/runtime'; +import { Subject } from 'rxjs'; -import { initDashboard, InitDashboardArgs } from './initDashboard'; -import { DashboardInitPhase, DashboardRoutes } from 'app/types'; +import { locationService, setEchoSrv } from '@grafana/runtime'; import { getBackendSrv } from 'app/core/services/backend_srv'; -import { dashboardInitCompleted, dashboardInitFetching, dashboardInitServices } from './reducers'; -import { Echo } from '../../../core/services/echo/Echo'; +import { keybindingSrv } from 'app/core/services/keybindingSrv'; import { variableAdapters } from 'app/features/variables/adapters'; import { createConstantVariableAdapter } from 'app/features/variables/constant/adapter'; import { constantBuilder } from 'app/features/variables/shared/testing/builders'; -import { initialTransactionState, variablesInitTransaction } from '../../variables/state/transactionReducer'; -import { keybindingSrv } from 'app/core/services/keybindingSrv'; -import { getTimeSrv, setTimeSrv } from '../services/TimeSrv'; -import { DashboardLoaderSrv, setDashboardLoaderSrv } from '../services/DashboardLoaderSrv'; -import { getDashboardSrv, setDashboardSrv } from '../services/DashboardSrv'; +import { DashboardInitPhase, DashboardRoutes } from 'app/types'; + +import { Echo } from '../../../core/services/echo/Echo'; import { getDashboardQueryRunner, setDashboardQueryRunnerFactory, } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; import { emptyResult } from '../../query/state/DashboardQueryRunner/utils'; -import { TransactionStatus } from '../../variables/types'; import { getPreloadedState } from '../../variables/state/helpers'; +import { initialTransactionState, variablesInitTransaction } from '../../variables/state/transactionReducer'; +import { TransactionStatus } from '../../variables/types'; +import { DashboardLoaderSrv, setDashboardLoaderSrv } from '../services/DashboardLoaderSrv'; +import { getDashboardSrv, setDashboardSrv } from '../services/DashboardSrv'; +import { getTimeSrv, setTimeSrv } from '../services/TimeSrv'; + +import { initDashboard, InitDashboardArgs } from './initDashboard'; +import { dashboardInitCompleted, dashboardInitFetching, dashboardInitServices } from './reducers'; jest.mock('app/core/services/backend_srv'); jest.mock('app/features/dashboard/services/TimeSrv', () => { diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 654bf8cdab1..c49b5f95628 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -1,25 +1,24 @@ -// Services & Utils +import { locationUtil, setWeekStart } from '@grafana/data'; +import { config, locationService } from '@grafana/runtime'; +import { notifyApp } from 'app/core/actions'; import { createErrorNotification } from 'app/core/copy/appNotification'; import { backendSrv } from 'app/core/services/backend_srv'; -import { DashboardSrv, getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; -import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { keybindingSrv } from 'app/core/services/keybindingSrv'; -// Actions -import { notifyApp } from 'app/core/actions'; -import { dashboardInitCompleted, dashboardInitFailed, dashboardInitFetching, dashboardInitServices } from './reducers'; -// Types -import { DashboardDTO, DashboardInitPhase, DashboardRoutes, StoreState, ThunkDispatch, ThunkResult } from 'app/types'; -import { DashboardModel } from './DashboardModel'; -import { locationUtil, setWeekStart } from '@grafana/data'; -import { initVariablesTransaction } from '../../variables/state/actions'; -import { emitDashboardViewEvent } from './analyticsProcessor'; -import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; -import { config, locationService } from '@grafana/runtime'; -import { createDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; -import { getIfExistsLastKey } from '../../variables/state/selectors'; -import { toStateKey } from 'app/features/variables/utils'; import store from 'app/core/store'; +import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; +import { DashboardSrv, getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { toStateKey } from 'app/features/variables/utils'; +import { DashboardDTO, DashboardInitPhase, DashboardRoutes, StoreState, ThunkDispatch, ThunkResult } from 'app/types'; + +import { createDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; +import { initVariablesTransaction } from '../../variables/state/actions'; +import { getIfExistsLastKey } from '../../variables/state/selectors'; + +import { DashboardModel } from './DashboardModel'; +import { emitDashboardViewEvent } from './analyticsProcessor'; +import { dashboardInitCompleted, dashboardInitFailed, dashboardInitFetching, dashboardInitServices } from './reducers'; export interface InitDashboardArgs { urlUid?: string; diff --git a/public/app/features/dashboard/state/reducers.test.ts b/public/app/features/dashboard/state/reducers.test.ts index f98a4fa6dec..ab2fa4a8c9e 100644 --- a/public/app/features/dashboard/state/reducers.test.ts +++ b/public/app/features/dashboard/state/reducers.test.ts @@ -1,3 +1,6 @@ +import { DashboardInitPhase, DashboardState, OrgRole, PermissionLevel } from 'app/types'; + +import { DashboardModel } from './DashboardModel'; import { dashboardInitCompleted, dashboardInitFailed, @@ -6,8 +9,6 @@ import { dashboardReducer, initialState, } from './reducers'; -import { DashboardInitPhase, DashboardState, OrgRole, PermissionLevel } from 'app/types'; -import { DashboardModel } from './DashboardModel'; describe('dashboard reducer', () => { describe('loadDashboardPermissions', () => { diff --git a/public/app/features/dashboard/state/reducers.ts b/public/app/features/dashboard/state/reducers.ts index 59cc5eadad2..4e57923e7c4 100644 --- a/public/app/features/dashboard/state/reducers.ts +++ b/public/app/features/dashboard/state/reducers.ts @@ -1,10 +1,12 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { DashboardAclDTO, DashboardInitError, DashboardInitPhase, DashboardState } from 'app/types'; + +import { PanelPlugin } from '@grafana/data'; import { AngularComponent } from '@grafana/runtime'; import { processAclItems } from 'app/core/utils/acl'; +import { DashboardAclDTO, DashboardInitError, DashboardInitPhase, DashboardState } from 'app/types'; + import { DashboardModel } from './DashboardModel'; import { PanelModel } from './PanelModel'; -import { PanelPlugin } from '@grafana/data'; export const initialState: DashboardState = { initPhase: DashboardInitPhase.NotStarted, diff --git a/public/app/features/dashboard/state/selectors.ts b/public/app/features/dashboard/state/selectors.ts index b0bf7529bbe..59d486d3a73 100644 --- a/public/app/features/dashboard/state/selectors.ts +++ b/public/app/features/dashboard/state/selectors.ts @@ -1,5 +1,6 @@ -import { StoreState } from 'app/types'; import { PanelPlugin } from '@grafana/data'; +import { StoreState } from 'app/types'; + import { getPanelPluginNotFound } from '../../panel/components/PanelPluginError'; export const getPanelPluginWithFallback = diff --git a/public/app/features/dashboard/state/utils.test.ts b/public/app/features/dashboard/state/utils.test.ts index fcaee378186..5c63afdf2ee 100644 --- a/public/app/features/dashboard/state/utils.test.ts +++ b/public/app/features/dashboard/state/utils.test.ts @@ -1,6 +1,7 @@ +import { REPEAT_DIR_HORIZONTAL } from '../../../core/constants'; + import { PanelModel } from './PanelModel'; import { deleteScopeVars, isOnTheSameGridRow } from './utils'; -import { REPEAT_DIR_HORIZONTAL } from '../../../core/constants'; describe('isOnTheSameGridRow', () => { describe('when source panel is next to a panel', () => { diff --git a/public/app/features/dashboard/state/utils.ts b/public/app/features/dashboard/state/utils.ts index 7a726900aeb..13ca54d302c 100644 --- a/public/app/features/dashboard/state/utils.ts +++ b/public/app/features/dashboard/state/utils.ts @@ -1,6 +1,7 @@ -import { PanelModel } from './PanelModel'; import { REPEAT_DIR_HORIZONTAL } from '../../../core/constants'; +import { PanelModel } from './PanelModel'; + export function isOnTheSameGridRow(sourcePanel: PanelModel, otherPanel: PanelModel): boolean { if (sourcePanel.repeatDirection === REPEAT_DIR_HORIZONTAL) { return false; diff --git a/public/app/features/dashboard/utils/getPanelMenu.test.ts b/public/app/features/dashboard/utils/getPanelMenu.test.ts index 9b2746f5141..6ca07eb7998 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.test.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.test.ts @@ -1,10 +1,12 @@ import { PanelMenuItem } from '@grafana/data'; -import { DashboardModel, PanelModel } from '../state'; -import { getPanelMenu } from './getPanelMenu'; -import { describe } from '../../../../test/lib/common'; -import { setStore } from 'app/store/store'; import config from 'app/core/config'; import * as actions from 'app/features/explore/state/main'; +import { setStore } from 'app/store/store'; + +import { describe } from '../../../../test/lib/common'; +import { DashboardModel, PanelModel } from '../state'; + +import { getPanelMenu } from './getPanelMenu'; jest.mock('app/core/services/context_srv', () => ({ contextSrv: { diff --git a/public/app/features/dashboard/utils/getPanelMenu.ts b/public/app/features/dashboard/utils/getPanelMenu.ts index 16c18b8501e..f5921d734b3 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.ts @@ -1,6 +1,9 @@ -import { store } from 'app/store/store'; -import { AngularComponent, getDataSourceSrv, locationService } from '@grafana/runtime'; import { PanelMenuItem } from '@grafana/data'; +import { AngularComponent, getDataSourceSrv, locationService } from '@grafana/runtime'; +import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; +import config from 'app/core/config'; +import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; +import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { addLibraryPanel, copyPanel, @@ -10,14 +13,12 @@ import { unlinkLibraryPanel, } from 'app/features/dashboard/utils/panel'; import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard'; -import { PanelModel } from 'app/features/dashboard/state/PanelModel'; -import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; +import { store } from 'app/store/store'; + import { contextSrv } from '../../../core/services/context_srv'; -import { navigateToExplore } from '../../explore/state/main'; import { getExploreUrl } from '../../../core/utils/explore'; +import { navigateToExplore } from '../../explore/state/main'; import { getTimeSrv } from '../services/TimeSrv'; -import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; -import config from 'app/core/config'; export function getPanelMenu( dashboard: DashboardModel, diff --git a/public/app/features/dashboard/utils/loadSnapshotData.ts b/public/app/features/dashboard/utils/loadSnapshotData.ts index 2a451cef341..40ae85cc49a 100644 --- a/public/app/features/dashboard/utils/loadSnapshotData.ts +++ b/public/app/features/dashboard/utils/loadSnapshotData.ts @@ -1,10 +1,12 @@ import { applyFieldOverrides, ArrayDataFrame, getDefaultTimeRange, LoadingState, PanelData } from '@grafana/data'; import { config } from 'app/core/config'; -import { DashboardModel, PanelModel } from '../state'; -import { getProcessedDataFrames } from '../../query/state/runRequest'; + import { SnapshotWorker } from '../../query/state/DashboardQueryRunner/SnapshotWorker'; -import { applyPanelTimeOverrides } from './panel'; +import { getProcessedDataFrames } from '../../query/state/runRequest'; import { getTimeSrv } from '../services/TimeSrv'; +import { DashboardModel, PanelModel } from '../state'; + +import { applyPanelTimeOverrides } from './panel'; export function loadSnapshotData(panel: PanelModel, dashboard: DashboardModel): PanelData { const data = getProcessedDataFrames(panel.snapshotData); diff --git a/public/app/features/dashboard/utils/panel.test.ts b/public/app/features/dashboard/utils/panel.test.ts index f1431af92a1..a979c7ab1cc 100644 --- a/public/app/features/dashboard/utils/panel.test.ts +++ b/public/app/features/dashboard/utils/panel.test.ts @@ -1,9 +1,11 @@ +import { advanceTo, clear } from 'jest-date-mock'; +import { ComponentClass } from 'react'; + import { dateTime, DateTime, PanelProps, TimeRange } from '@grafana/data'; import { applyPanelTimeOverrides, calculateInnerPanelHeight } from 'app/features/dashboard/utils/panel'; -import { advanceTo, clear } from 'jest-date-mock'; -import { PanelModel } from '../state'; + import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; -import { ComponentClass } from 'react'; +import { PanelModel } from '../state'; const dashboardTimeRange: TimeRange = { from: dateTime([2019, 1, 11, 12, 0]), diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index d92497a43f0..202de4be4f4 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -1,28 +1,20 @@ -// Store -import store from 'app/core/store'; -import { dispatch } from 'app/store/store'; - -// Models -import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; -import { PanelModel } from 'app/features/dashboard/state/PanelModel'; -import { TimeRange, AppEvents, rangeUtil, dateMath, PanelModel as IPanelModel } from '@grafana/data'; - -// Utils import { isString as _isString } from 'lodash'; + +import { TimeRange, AppEvents, rangeUtil, dateMath, PanelModel as IPanelModel } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import config from 'app/core/config'; - -// Services -import { getTemplateSrv } from '@grafana/runtime'; - -// Constants import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants'; - +import store from 'app/core/store'; import { ShareModal } from 'app/features/dashboard/components/ShareModal'; -import { ShowConfirmModalEvent, ShowModalReactEvent } from '../../../types/events'; +import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; +import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { AddLibraryPanelModal } from 'app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal'; import { UnlinkModal } from 'app/features/library-panels/components/UnlinkModal/UnlinkModal'; import { cleanUpPanelState } from 'app/features/panel/state/actions'; +import { dispatch } from 'app/store/store'; + +import { ShowConfirmModalEvent, ShowModalReactEvent } from '../../../types/events'; export const removePanel = (dashboard: DashboardModel, panel: PanelModel, ask: boolean) => { // confirm deletion diff --git a/public/app/features/dashboard/utils/panelMerge.test.ts b/public/app/features/dashboard/utils/panelMerge.test.ts index d0117f532ad..9882379d3a2 100644 --- a/public/app/features/dashboard/utils/panelMerge.test.ts +++ b/public/app/features/dashboard/utils/panelMerge.test.ts @@ -1,4 +1,5 @@ import { PanelModel } from '@grafana/data'; + import { DashboardModel } from '../state/DashboardModel'; describe('Merge dashbaord panels', () => { diff --git a/public/app/features/dashboard/utils/panelMerge.ts b/public/app/features/dashboard/utils/panelMerge.ts index a0de905acbd..946d3177cf0 100644 --- a/public/app/features/dashboard/utils/panelMerge.ts +++ b/public/app/features/dashboard/utils/panelMerge.ts @@ -1,5 +1,7 @@ -import { PanelModel as IPanelModel } from '@grafana/data'; import { isEqualWith } from 'lodash'; + +import { PanelModel as IPanelModel } from '@grafana/data'; + import { PanelModel } from '../state'; export interface PanelMergeInfo { diff --git a/public/app/features/datasources/DashboardsTable.test.tsx b/public/app/features/datasources/DashboardsTable.test.tsx index 10a14f464e4..4e6fafe722f 100644 --- a/public/app/features/datasources/DashboardsTable.test.tsx +++ b/public/app/features/datasources/DashboardsTable.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import DashboardsTable, { Props } from './DashboardsTable'; +import React from 'react'; + import { PluginDashboard } from '../../types'; +import DashboardsTable, { Props } from './DashboardsTable'; + const setup = (propOverrides?: object) => { const props: Props = { dashboards: [] as PluginDashboard[], diff --git a/public/app/features/datasources/DashboardsTable.tsx b/public/app/features/datasources/DashboardsTable.tsx index da71c9de227..3e45624871c 100644 --- a/public/app/features/datasources/DashboardsTable.tsx +++ b/public/app/features/datasources/DashboardsTable.tsx @@ -1,7 +1,9 @@ import React, { FC } from 'react'; -import { PluginDashboard } from '../../types'; + import { Button, Icon } from '@grafana/ui'; +import { PluginDashboard } from '../../types'; + export interface Props { dashboards: PluginDashboard[]; onImport: (dashboard: PluginDashboard, overwrite: boolean) => void; diff --git a/public/app/features/datasources/DataSourceDashboards.test.tsx b/public/app/features/datasources/DataSourceDashboards.test.tsx index b90133586fa..516b90f6570 100644 --- a/public/app/features/datasources/DataSourceDashboards.test.tsx +++ b/public/app/features/datasources/DataSourceDashboards.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { DataSourceDashboards, Props } from './DataSourceDashboards'; +import React from 'react'; + import { DataSourceSettings, NavModel } from '@grafana/data'; -import { PluginDashboard } from 'app/types'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { PluginDashboard } from 'app/types'; + +import { DataSourceDashboards, Props } from './DataSourceDashboards'; const setup = (propOverrides?: object) => { const props: Props = { diff --git a/public/app/features/datasources/DataSourceDashboards.tsx b/public/app/features/datasources/DataSourceDashboards.tsx index f035776ddae..9673716ddc3 100644 --- a/public/app/features/datasources/DataSourceDashboards.tsx +++ b/public/app/features/datasources/DataSourceDashboards.tsx @@ -1,21 +1,17 @@ -// Libraries import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -// Components import Page from 'app/core/components/Page/Page'; -import DashboardTable from './DashboardsTable'; - -// Actions & Selectors -import { getNavModel } from 'app/core/selectors/navModel'; -import { loadDataSource } from './state/actions'; -import { loadPluginDashboards } from '../plugins/admin/state/actions'; -import { importDashboard, removeDashboard } from '../dashboard/state/actions'; -import { getDataSource } from './state/selectors'; - -// Types -import { PluginDashboard, StoreState } from 'app/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { PluginDashboard, StoreState } from 'app/types'; + +import { importDashboard, removeDashboard } from '../dashboard/state/actions'; +import { loadPluginDashboards } from '../plugins/admin/state/actions'; + +import DashboardTable from './DashboardsTable'; +import { loadDataSource } from './state/actions'; +import { getDataSource } from './state/selectors'; export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} diff --git a/public/app/features/datasources/DataSourceList.test.tsx b/public/app/features/datasources/DataSourceList.test.tsx index 55c24291bce..3110b35a6e8 100644 --- a/public/app/features/datasources/DataSourceList.test.tsx +++ b/public/app/features/datasources/DataSourceList.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { LayoutModes } from '@grafana/data'; + import DataSourcesList from './DataSourcesList'; import { getMockDataSources } from './__mocks__/dataSourcesMocks'; -import { LayoutModes } from '@grafana/data'; const setup = () => { const props = { diff --git a/public/app/features/datasources/DataSourcesList.tsx b/public/app/features/datasources/DataSourcesList.tsx index 3566177d7c0..9bcca5d543c 100644 --- a/public/app/features/datasources/DataSourcesList.tsx +++ b/public/app/features/datasources/DataSourcesList.tsx @@ -1,10 +1,10 @@ // Libraries +import { css } from '@emotion/css'; import React, { FC } from 'react'; // Types import { DataSourceSettings, LayoutMode } from '@grafana/data'; import { Card, Tag, useStyles } from '@grafana/ui'; -import { css } from '@emotion/css'; export interface Props { dataSources: DataSourceSettings[]; diff --git a/public/app/features/datasources/DataSourcesListPage.test.tsx b/public/app/features/datasources/DataSourcesListPage.test.tsx index 975f925c8b3..c26bd1af413 100644 --- a/public/app/features/datasources/DataSourcesListPage.test.tsx +++ b/public/app/features/datasources/DataSourcesListPage.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { DataSourceSettings, NavModel, LayoutModes } from '@grafana/data'; import { DataSourcesListPage, Props } from './DataSourcesListPage'; diff --git a/public/app/features/datasources/DataSourcesListPage.tsx b/public/app/features/datasources/DataSourcesListPage.tsx index 4b1a5101f96..ca8c5d3ec20 100644 --- a/public/app/features/datasources/DataSourcesListPage.tsx +++ b/public/app/features/datasources/DataSourcesListPage.tsx @@ -1,27 +1,23 @@ -// Libraries import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -// Services & Utils -import { contextSrv } from 'app/core/core'; -// Components + +import { IconName } from '@grafana/ui'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import Page from 'app/core/components/Page/Page'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import DataSourcesList from './DataSourcesList'; -// Types -import { IconName } from '@grafana/ui'; -import { StoreState, AccessControlAction } from 'app/types'; -// Actions -import { loadDataSources } from './state/actions'; +import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState, AccessControlAction } from 'app/types'; +import DataSourcesList from './DataSourcesList'; +import { loadDataSources } from './state/actions'; +import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/reducers'; import { getDataSources, getDataSourcesCount, getDataSourcesLayoutMode, getDataSourcesSearchQuery, } from './state/selectors'; -import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/reducers'; function mapStateToProps(state: StoreState) { return { diff --git a/public/app/features/datasources/NewDataSourcePage.tsx b/public/app/features/datasources/NewDataSourcePage.tsx index 21b68754131..b7943e8af90 100644 --- a/public/app/features/datasources/NewDataSourcePage.tsx +++ b/public/app/features/datasources/NewDataSourcePage.tsx @@ -1,17 +1,19 @@ +import { css, cx } from '@emotion/css'; import React, { FC, PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { DataSourcePluginMeta, GrafanaTheme2, NavModel } from '@grafana/data'; -import { Card, LinkButton, List, PluginSignatureBadge, FilterInput, useStyles2 } from '@grafana/ui'; -import { css, cx } from '@emotion/css'; -import { selectors } from '@grafana/e2e-selectors'; +import { DataSourcePluginMeta, GrafanaTheme2, NavModel } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Card, LinkButton, List, PluginSignatureBadge, FilterInput, useStyles2 } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import { StoreState } from 'app/types'; -import { addDataSource, loadDataSourcePlugins } from './state/actions'; -import { getDataSourcePlugins } from './state/selectors'; -import { setDataSourceTypeSearchQuery } from './state/reducers'; + import { PluginsErrorsInfo } from '../plugins/components/PluginsErrorsInfo'; +import { addDataSource, loadDataSourcePlugins } from './state/actions'; +import { setDataSourceTypeSearchQuery } from './state/reducers'; +import { getDataSourcePlugins } from './state/selectors'; + function mapStateToProps(state: StoreState) { return { navModel: getNavModel(), diff --git a/public/app/features/datasources/settings/BasicSettings.test.tsx b/public/app/features/datasources/settings/BasicSettings.test.tsx index 0194adc9007..389c6618fcc 100644 --- a/public/app/features/datasources/settings/BasicSettings.test.tsx +++ b/public/app/features/datasources/settings/BasicSettings.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import BasicSettings, { Props } from './BasicSettings'; const setup = () => { diff --git a/public/app/features/datasources/settings/BasicSettings.tsx b/public/app/features/datasources/settings/BasicSettings.tsx index 1271672e6e6..ad9d7d49f72 100644 --- a/public/app/features/datasources/settings/BasicSettings.tsx +++ b/public/app/features/datasources/settings/BasicSettings.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; -import { InlineFormLabel, LegacyForms } from '@grafana/ui'; + import { selectors } from '@grafana/e2e-selectors'; +import { InlineFormLabel, LegacyForms } from '@grafana/ui'; const { Input, Switch } = LegacyForms; diff --git a/public/app/features/datasources/settings/ButtonRow.test.tsx b/public/app/features/datasources/settings/ButtonRow.test.tsx index aa713dd557d..9508cfd8e97 100644 --- a/public/app/features/datasources/settings/ButtonRow.test.tsx +++ b/public/app/features/datasources/settings/ButtonRow.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import ButtonRow, { Props } from './ButtonRow'; jest.mock('app/core/core', () => { diff --git a/public/app/features/datasources/settings/ButtonRow.tsx b/public/app/features/datasources/settings/ButtonRow.tsx index e2907d72889..473043ac451 100644 --- a/public/app/features/datasources/settings/ButtonRow.tsx +++ b/public/app/features/datasources/settings/ButtonRow.tsx @@ -1,10 +1,9 @@ import React, { FC } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; - import { Button, LinkButton } from '@grafana/ui'; - -import { AccessControlAction } from 'app/types/'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction } from 'app/types/'; export interface Props { exploreUrl: string; diff --git a/public/app/features/datasources/settings/CloudInfoBox.tsx b/public/app/features/datasources/settings/CloudInfoBox.tsx index 96990bf434f..ce087cd6148 100644 --- a/public/app/features/datasources/settings/CloudInfoBox.tsx +++ b/public/app/features/datasources/settings/CloudInfoBox.tsx @@ -1,9 +1,10 @@ -import { DataSourceSettings } from '@grafana/data'; -import { Alert } from '@grafana/ui'; import React, { FC } from 'react'; -import { config } from 'app/core/config'; + +import { DataSourceSettings } from '@grafana/data'; import { GrafanaEdition } from '@grafana/data/src/types/config'; +import { Alert } from '@grafana/ui'; import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider'; +import { config } from 'app/core/config'; const LOCAL_STORAGE_KEY = 'datasources.settings.cloudInfoBox.isDismissed'; diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx index 12f7226151b..57e0d50c8a6 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.test.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { DataSourceSettingsPage, Props } from './DataSourceSettingsPage'; -import { getMockDataSource } from '../__mocks__/dataSourcesMocks'; -import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; -import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; -import { cleanUpAction } from 'app/core/actions/cleanUp'; import { screen, render } from '@testing-library/react'; -import { selectors } from '@grafana/e2e-selectors'; +import React from 'react'; + import { PluginState } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { cleanUpAction } from 'app/core/actions/cleanUp'; +import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; + +import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; +import { getMockDataSource } from '../__mocks__/dataSourcesMocks'; +import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers'; + +import { DataSourceSettingsPage, Props } from './DataSourceSettingsPage'; jest.mock('app/core/core', () => { return { diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index f4d12900901..68e2a4f2384 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -1,15 +1,19 @@ import React, { PureComponent } from 'react'; -// Components -import Page from 'app/core/components/Page/Page'; -import { PluginSettings } from './PluginSettings'; -import BasicSettings from './BasicSettings'; -import ButtonRow from './ButtonRow'; -// Services & Utils -import appEvents from 'app/core/app_events'; -import { contextSrv } from 'app/core/core'; +import { connect, ConnectedProps } from 'react-redux'; -// Actions & selectors -import { getDataSource, getDataSourceMeta } from '../state/selectors'; +import { DataSourceSettings, urlUtil } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Alert, Button } from '@grafana/ui'; +import { cleanUpAction } from 'app/core/actions/cleanUp'; +import appEvents from 'app/core/app_events'; +import Page from 'app/core/components/Page/Page'; +import { contextSrv } from 'app/core/core'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; +import { StoreState, AccessControlAction } from 'app/types/'; + +import { ShowConfirmModalEvent } from '../../../types/events'; import { deleteDataSource, initDataSourceSettings, @@ -17,21 +21,14 @@ import { testDataSource, updateDataSource, } from '../state/actions'; -import { getNavModel } from 'app/core/selectors/navModel'; - -// Types -import { StoreState, AccessControlAction } from 'app/types/'; -import { DataSourceSettings, urlUtil } from '@grafana/data'; -import { Alert, Button } from '@grafana/ui'; import { getDataSourceLoadingNav, buildNavModel, getDataSourceNav } from '../state/navModel'; -import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers'; -import { selectors } from '@grafana/e2e-selectors'; +import { getDataSource, getDataSourceMeta } from '../state/selectors'; + +import BasicSettings from './BasicSettings'; +import ButtonRow from './ButtonRow'; import { CloudInfoBox } from './CloudInfoBox'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { connect, ConnectedProps } from 'react-redux'; -import { cleanUpAction } from 'app/core/actions/cleanUp'; -import { ShowConfirmModalEvent } from '../../../types/events'; +import { PluginSettings } from './PluginSettings'; export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} diff --git a/public/app/features/datasources/settings/PluginSettings.tsx b/public/app/features/datasources/settings/PluginSettings.tsx index 429f3de6e98..ca322ff2cc5 100644 --- a/public/app/features/datasources/settings/PluginSettings.tsx +++ b/public/app/features/datasources/settings/PluginSettings.tsx @@ -1,5 +1,6 @@ -import React, { PureComponent } from 'react'; import { cloneDeep } from 'lodash'; +import React, { PureComponent } from 'react'; + import { DataQuery, DataSourceApi, diff --git a/public/app/features/datasources/state/actions.test.ts b/public/app/features/datasources/state/actions.test.ts index 8d652cdcd72..84e6ca9c108 100644 --- a/public/app/features/datasources/state/actions.test.ts +++ b/public/app/features/datasources/state/actions.test.ts @@ -1,3 +1,14 @@ +import { of } from 'rxjs'; +import { thunkTester } from 'test/core/thunk/thunkTester'; + +import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; +import { getBackendSrv } from 'app/core/services/backend_srv'; +import { ThunkResult, ThunkDispatch } from 'app/types'; + +import { getMockPlugin, getMockPlugins } from '../../plugins/__mocks__/pluginMocks'; +import { GenericDataSourcePlugin } from '../settings/PluginSettings'; +import { initDataSourceSettings } from '../state/actions'; + import { findNewName, nameExits, @@ -6,8 +17,6 @@ import { TestDataSourceDependencies, getDataSourceUsingUidOrId, } from './actions'; -import { getMockPlugin, getMockPlugins } from '../../plugins/__mocks__/pluginMocks'; -import { thunkTester } from 'test/core/thunk/thunkTester'; import { initDataSourceSettingsSucceeded, initDataSourceSettingsFailed, @@ -15,12 +24,6 @@ import { testDataSourceSucceeded, testDataSourceFailed, } from './reducers'; -import { initDataSourceSettings } from '../state/actions'; -import { ThunkResult, ThunkDispatch } from 'app/types'; -import { GenericDataSourcePlugin } from '../settings/PluginSettings'; -import { getBackendSrv } from 'app/core/services/backend_srv'; -import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; -import { of } from 'rxjs'; jest.mock('app/core/services/backend_srv'); jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 54fa40cc03d..9dd8d5d26e9 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -1,13 +1,17 @@ import { lastValueFrom } from 'rxjs'; + import { DataSourcePluginMeta, DataSourceSettings, locationUtil } from '@grafana/data'; import { DataSourceWithBackend, getDataSourceSrv, locationService } from '@grafana/runtime'; import { updateNavIndex } from 'app/core/actions'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { accessControlQueryParam } from 'app/core/utils/accessControl'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; +import { importDataSourcePlugin } from 'app/features/plugins/plugin_loader'; import { DataSourcePluginCategory, ThunkDispatch, ThunkResult } from 'app/types'; +import { contextSrv } from '../../../core/services/context_srv'; + import { buildCategories } from './buildCategories'; import { buildNavModel } from './navModel'; import { @@ -23,8 +27,6 @@ import { testDataSourceSucceeded, } from './reducers'; import { getDataSource, getDataSourceMeta } from './selectors'; -import { accessControlQueryParam } from 'app/core/utils/accessControl'; -import { contextSrv } from '../../../core/services/context_srv'; export interface DataSourceTypesLoadedPayload { plugins: DataSourcePluginMeta[]; diff --git a/public/app/features/datasources/state/buildCategories.test.ts b/public/app/features/datasources/state/buildCategories.test.ts index 04076b74e13..5bea2aa63d1 100644 --- a/public/app/features/datasources/state/buildCategories.test.ts +++ b/public/app/features/datasources/state/buildCategories.test.ts @@ -1,7 +1,9 @@ -import { buildCategories } from './buildCategories'; -import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; import { DataSourcePluginMeta } from '@grafana/data'; +import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; + +import { buildCategories } from './buildCategories'; + const plugins: DataSourcePluginMeta[] = [ { ...getMockPlugin({ id: 'graphite' }), diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts index 24c82d75074..4e0b1ed593d 100644 --- a/public/app/features/datasources/state/navModel.ts +++ b/public/app/features/datasources/state/navModel.ts @@ -1,9 +1,11 @@ import { DataSourceSettings, PluginType, PluginInclude, NavModel, NavModelItem } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; +import { ProBadge } from 'app/core/components/Upgrade/ProBadge'; import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; -import { ProBadge } from 'app/core/components/Upgrade/ProBadge'; + +import { highlightTrial } from '../../admin/utils'; import { GenericDataSourcePlugin } from '../settings/PluginSettings'; const loadingDSType = 'Loading'; @@ -53,7 +55,8 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat const isLoadingNav = dataSource.type === loadingDSType; - const dsPermissions = { + const permissionsExperimentId = 'feature-highlights-data-source-permissions-badge'; + const dsPermissions: NavModelItem = { active: false, icon: 'lock', id: `datasource-permissions-${dataSource.uid}`, @@ -61,6 +64,10 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat url: `datasources/edit/${dataSource.uid}/permissions`, }; + if (highlightTrial() && !isLoadingNav) { + dsPermissions.tabSuffix = () => ProBadge({ experimentId: permissionsExperimentId, eventVariant: 'trial' }); + } + if (featureEnabled('dspermissions')) { if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) { navModel.children!.push(dsPermissions); @@ -69,11 +76,12 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat navModel.children!.push({ ...dsPermissions, url: dsPermissions.url + '/upgrade', - tabSuffix: () => ProBadge({ experimentId: 'feature-highlights-data-source-permissions-badge' }), + tabSuffix: () => ProBadge({ experimentId: permissionsExperimentId }), }); } - const analytics = { + const analyticsExperimentId = 'feature-highlights-data-source-insights-badge'; + const analytics: NavModelItem = { active: false, icon: 'info-circle', id: `datasource-insights-${dataSource.uid}`, @@ -81,17 +89,23 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat url: `datasources/edit/${dataSource.uid}/insights`, }; + if (highlightTrial() && !isLoadingNav) { + analytics.tabSuffix = () => ProBadge({ experimentId: analyticsExperimentId, eventVariant: 'trial' }); + } + if (featureEnabled('analytics')) { navModel.children!.push(analytics); } else if (highlightsEnabled && !isLoadingNav) { navModel.children!.push({ ...analytics, url: analytics.url + '/upgrade', - tabSuffix: () => ProBadge({ experimentId: 'feature-highlights-data-source-insights-badge' }), + tabSuffix: () => ProBadge({ experimentId: analyticsExperimentId }), }); } - const caching = { + const cachingExperimentId = 'feature-highlights-query-caching-badge'; + + const caching: NavModelItem = { active: false, icon: 'database', id: `datasource-cache-${dataSource.uid}`, @@ -100,13 +114,17 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat hideFromTabs: !pluginMeta.isBackend || !config.caching.enabled, }; + if (highlightTrial() && !isLoadingNav) { + caching.tabSuffix = () => ProBadge({ experimentId: cachingExperimentId, eventVariant: 'trial' }); + } + if (featureEnabled('caching')) { navModel.children!.push(caching); } else if (highlightsEnabled && !isLoadingNav) { navModel.children!.push({ ...caching, url: caching.url + '/upgrade', - tabSuffix: () => ProBadge({ experimentId: 'feature-highlights-query-caching-badge' }), + tabSuffix: () => ProBadge({ experimentId: cachingExperimentId }), }); } diff --git a/public/app/features/datasources/state/reducers.test.ts b/public/app/features/datasources/state/reducers.test.ts index cb5e6afed54..f6d409fd5a6 100644 --- a/public/app/features/datasources/state/reducers.test.ts +++ b/public/app/features/datasources/state/reducers.test.ts @@ -1,4 +1,11 @@ import { reducerTester } from 'test/core/redux/reducerTester'; + +import { PluginMeta, PluginMetaInfo, PluginType, LayoutModes } from '@grafana/data'; +import { DataSourceSettingsState, DataSourcesState } from 'app/types'; + +import { getMockDataSource, getMockDataSources } from '../__mocks__/dataSourcesMocks'; +import { GenericDataSourcePlugin } from '../settings/PluginSettings'; + import { dataSourceLoaded, dataSourceMetaLoaded, @@ -17,10 +24,6 @@ import { setDataSourceTypeSearchQuery, setIsDefault, } from './reducers'; -import { getMockDataSource, getMockDataSources } from '../__mocks__/dataSourcesMocks'; -import { DataSourceSettingsState, DataSourcesState } from 'app/types'; -import { PluginMeta, PluginMetaInfo, PluginType, LayoutModes } from '@grafana/data'; -import { GenericDataSourcePlugin } from '../settings/PluginSettings'; const mockPlugin = () => ({ diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 1af1f762d6b..8410ec5aab6 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -1,10 +1,12 @@ import { AnyAction, createAction } from '@reduxjs/toolkit'; -import { DataSourcePluginMeta, DataSourceSettings, LayoutMode, LayoutModes } from '@grafana/data'; +import { DataSourcePluginMeta, DataSourceSettings, LayoutMode, LayoutModes } from '@grafana/data'; import { DataSourcesState, DataSourceSettingsState, TestingStatus } from 'app/types'; -import { DataSourceTypesLoadedPayload } from './actions'; + import { GenericDataSourcePlugin } from '../settings/PluginSettings'; +import { DataSourceTypesLoadedPayload } from './actions'; + export const initialState: DataSourcesState = { dataSources: [], plugins: [], diff --git a/public/app/features/datasources/state/selectors.ts b/public/app/features/datasources/state/selectors.ts index b09e79698f4..558dfa88a4e 100644 --- a/public/app/features/datasources/state/selectors.ts +++ b/public/app/features/datasources/state/selectors.ts @@ -1,4 +1,5 @@ import { DataSourcePluginMeta, DataSourceSettings, UrlQueryValue } from '@grafana/data'; + import { DataSourcesState } from '../../../types/datasources'; export const getDataSources = (state: DataSourcesState) => { diff --git a/public/app/features/dimensions/color.ts b/public/app/features/dimensions/color.ts index d38a3fc34be..5e6646db95c 100644 --- a/public/app/features/dimensions/color.ts +++ b/public/app/features/dimensions/color.ts @@ -1,4 +1,5 @@ import { DataFrame, Field, getDisplayProcessor, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; + import { ColorDimensionConfig, DimensionSupplier } from './types'; import { findField, getLastNotNullFieldValue } from './utils'; diff --git a/public/app/features/dimensions/editors/ColorDimensionEditor.tsx b/public/app/features/dimensions/editors/ColorDimensionEditor.tsx index 823fa1989c5..1b18ddedc60 100644 --- a/public/app/features/dimensions/editors/ColorDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ColorDimensionEditor.tsx @@ -1,12 +1,14 @@ +import { css } from '@emotion/css'; import React, { FC, useCallback } from 'react'; + import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data'; -import { ColorDimensionConfig } from '../types'; import { Select, ColorPicker, useStyles2 } from '@grafana/ui'; + import { useFieldDisplayNames, useSelectOptions, } from '../../../../../packages/grafana-ui/src/components/MatchersUI/utils'; -import { css } from '@emotion/css'; +import { ColorDimensionConfig } from '../types'; const fixedColorOption: SelectableValue = { label: 'Fixed color', diff --git a/public/app/features/dimensions/editors/FolderPickerTab.tsx b/public/app/features/dimensions/editors/FolderPickerTab.tsx index 13fa478d577..005566852c6 100644 --- a/public/app/features/dimensions/editors/FolderPickerTab.tsx +++ b/public/app/features/dimensions/editors/FolderPickerTab.tsx @@ -1,11 +1,13 @@ -import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { Field, FilterInput, Select, useStyles2 } from '@grafana/ui'; +import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; + import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { Field, FilterInput, Select, useStyles2 } from '@grafana/ui'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; import { MediaType, ResourceFolderName } from '../types'; -import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; + import { ResourceCards } from './ResourceCards'; const getFolders = (mediaType: MediaType) => { diff --git a/public/app/features/dimensions/editors/IconSelector.tsx b/public/app/features/dimensions/editors/IconSelector.tsx index 3a2d977c9a7..58188cc383e 100644 --- a/public/app/features/dimensions/editors/IconSelector.tsx +++ b/public/app/features/dimensions/editors/IconSelector.tsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; -import { Select } from '@grafana/ui'; + import { SelectableValue } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; +import { Select } from '@grafana/ui'; interface Props { value: string; diff --git a/public/app/features/dimensions/editors/NumberInput.tsx b/public/app/features/dimensions/editors/NumberInput.tsx index da21132a776..24e8b281c13 100644 --- a/public/app/features/dimensions/editors/NumberInput.tsx +++ b/public/app/features/dimensions/editors/NumberInput.tsx @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; + import { Field, Input } from '@grafana/ui'; interface Props { diff --git a/public/app/features/dimensions/editors/ResourceCards.tsx b/public/app/features/dimensions/editors/ResourceCards.tsx index aeaa1fc6758..afd7e0d6bd1 100644 --- a/public/app/features/dimensions/editors/ResourceCards.tsx +++ b/public/app/features/dimensions/editors/ResourceCards.tsx @@ -1,10 +1,12 @@ +import { css, cx } from '@emotion/css'; import React, { memo, CSSProperties } from 'react'; -import { areEqual, FixedSizeGrid as Grid } from 'react-window'; +import SVG from 'react-inlinesvg'; import AutoSizer from 'react-virtualized-auto-sizer'; +import { areEqual, FixedSizeGrid as Grid } from 'react-window'; + import { GrafanaTheme2 } from '@grafana/data'; import { useTheme2, stylesFactory } from '@grafana/ui'; -import SVG from 'react-inlinesvg'; -import { css, cx } from '@emotion/css'; + import { ResourceItem } from './FolderPickerTab'; interface CellProps { diff --git a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx index 6818ab6ee37..a9020f991c2 100644 --- a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx @@ -1,8 +1,10 @@ import React, { FC, useCallback } from 'react'; + import { FieldNamePickerConfigSettings, StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data'; import { InlineField, InlineFieldRow, RadioButtonGroup } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker'; +import { getPublicOrAbsoluteUrl, ResourceFolderName } from '..'; import { MediaType, ResourceDimensionConfig, @@ -10,7 +12,7 @@ import { ResourceDimensionOptions, ResourcePickerSize, } from '../types'; -import { getPublicOrAbsoluteUrl, ResourceFolderName } from '..'; + import { ResourcePicker } from './ResourcePicker'; const resourceOptions = [ diff --git a/public/app/features/dimensions/editors/ResourcePicker.tsx b/public/app/features/dimensions/editors/ResourcePicker.tsx index 938792bc14d..7bc55df26b7 100644 --- a/public/app/features/dimensions/editors/ResourcePicker.tsx +++ b/public/app/features/dimensions/editors/ResourcePicker.tsx @@ -1,5 +1,8 @@ -import React, { createRef } from 'react'; import { css } from '@emotion/css'; +import React, { createRef } from 'react'; +import SVG from 'react-inlinesvg'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, InlineField, @@ -11,13 +14,12 @@ import { useStyles2, useTheme2, } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import SVG from 'react-inlinesvg'; - -import { MediaType, ResourceFolderName, ResourcePickerSize } from '../types'; import { closePopover } from '@grafana/ui/src/utils/closePopover'; -import { ResourcePickerPopover } from './ResourcePickerPopover'; + import { getPublicOrAbsoluteUrl } from '../resource'; +import { MediaType, ResourceFolderName, ResourcePickerSize } from '../types'; + +import { ResourcePickerPopover } from './ResourcePickerPopover'; interface Props { onChange: (value?: string) => void; diff --git a/public/app/features/dimensions/editors/ResourcePickerPopover.tsx b/public/app/features/dimensions/editors/ResourcePickerPopover.tsx index c38df5788f3..66baddbbcbd 100644 --- a/public/app/features/dimensions/editors/ResourcePickerPopover.tsx +++ b/public/app/features/dimensions/editors/ResourcePickerPopover.tsx @@ -1,12 +1,14 @@ -import React, { createRef, useState } from 'react'; import { css } from '@emotion/css'; -import { Button, ButtonGroup, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import { FocusScope } from '@react-aria/focus'; import { useDialog } from '@react-aria/dialog'; +import { FocusScope } from '@react-aria/focus'; import { useOverlay } from '@react-aria/overlays'; +import React, { createRef, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, ButtonGroup, useStyles2 } from '@grafana/ui'; import { MediaType, PickerTabType, ResourceFolderName } from '../types'; + import { FolderPickerTab } from './FolderPickerTab'; import { URLPickerTab } from './URLPickerTab'; diff --git a/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx b/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx index 4efef4cb0ec..dc6bc6c4de7 100644 --- a/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ScalarDimensionEditor.tsx @@ -1,10 +1,13 @@ +import { css } from '@emotion/css'; import React, { FC, useCallback } from 'react'; + import { FieldType, GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data'; -import { ScalarDimensionConfig, ScalarDimensionMode, ScalarDimensionOptions } from '../types'; import { InlineField, InlineFieldRow, RadioButtonGroup, Select, useStyles2 } from '@grafana/ui'; import { useFieldDisplayNames, useSelectOptions } from '@grafana/ui/src/components/MatchersUI/utils'; + +import { ScalarDimensionConfig, ScalarDimensionMode, ScalarDimensionOptions } from '../types'; + import { NumberInput } from './NumberInput'; -import { css } from '@emotion/css'; const fixedValueOption: SelectableValue = { label: 'Fixed value', diff --git a/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx b/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx index 7162a82e86e..f57bd2f0fbe 100644 --- a/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ScaleDimensionEditor.tsx @@ -1,14 +1,17 @@ +import { css } from '@emotion/css'; import React, { FC, useCallback, useMemo } from 'react'; + import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data'; -import { ScaleDimensionConfig, ScaleDimensionOptions } from '../types'; import { InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui'; + import { useFieldDisplayNames, useSelectOptions, } from '../../../../../packages/grafana-ui/src/components/MatchersUI/utils'; -import { NumberInput } from './NumberInput'; -import { css } from '@emotion/css'; import { validateScaleOptions, validateScaleConfig } from '../scale'; +import { ScaleDimensionConfig, ScaleDimensionOptions } from '../types'; + +import { NumberInput } from './NumberInput'; const fixedValueOption: SelectableValue = { label: 'Fixed value', diff --git a/public/app/features/dimensions/editors/TextDimensionEditor.tsx b/public/app/features/dimensions/editors/TextDimensionEditor.tsx index 0c452fd58b8..6688bbf23ee 100644 --- a/public/app/features/dimensions/editors/TextDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/TextDimensionEditor.tsx @@ -1,4 +1,5 @@ import React, { FC, useCallback } from 'react'; + import { FieldNamePickerConfigSettings, StandardEditorProps, @@ -7,8 +8,8 @@ import { } from '@grafana/data'; import { Button, InlineField, InlineFieldRow, RadioButtonGroup, StringValueEditor } from '@grafana/ui'; -import { TextDimensionConfig, TextDimensionMode, TextDimensionOptions } from '../types'; import { FieldNamePicker } from '../../../../../packages/grafana-ui/src/components/MatchersUI/FieldNamePicker'; +import { TextDimensionConfig, TextDimensionMode, TextDimensionOptions } from '../types'; const textOptions = [ { label: 'Fixed', value: TextDimensionMode.Fixed, description: 'Fixed value' }, diff --git a/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx b/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx index f7213e925fa..c680e51c66f 100644 --- a/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx +++ b/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx @@ -1,9 +1,11 @@ -import React, { ChangeEvent } from 'react'; import { mount } from 'enzyme'; +import React, { ChangeEvent } from 'react'; + import { createTheme, ThresholdsMode } from '@grafana/data'; -import { ThresholdsEditor, Props, thresholdsWithoutKey } from './ThresholdsEditor'; import { mockThemeContext, colors } from '@grafana/ui'; +import { ThresholdsEditor, Props, thresholdsWithoutKey } from './ThresholdsEditor'; + const setup = (propOverrides?: Partial) => { const props: Props = { onChange: jest.fn(), diff --git a/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx b/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx index e79c84a0084..2e1c44a78eb 100644 --- a/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx +++ b/public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx @@ -1,5 +1,7 @@ -import React, { PureComponent, ChangeEvent } from 'react'; import { css } from '@emotion/css'; +import { isNumber } from 'lodash'; +import React, { PureComponent, ChangeEvent } from 'react'; + import { Threshold, sortThresholds, @@ -8,7 +10,6 @@ import { SelectableValue, GrafanaTheme, } from '@grafana/data'; -import { isNumber } from 'lodash'; import { Input, colors, diff --git a/public/app/features/dimensions/editors/ThresholdsEditor/thresholds.tsx b/public/app/features/dimensions/editors/ThresholdsEditor/thresholds.tsx index 9900698a6f9..1642c083fe1 100644 --- a/public/app/features/dimensions/editors/ThresholdsEditor/thresholds.tsx +++ b/public/app/features/dimensions/editors/ThresholdsEditor/thresholds.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { FieldConfigEditorProps, ThresholdsConfig, ThresholdsMode, ThresholdsFieldConfigSettings } from '@grafana/data'; + import { ThresholdsEditor } from './ThresholdsEditor'; export class ThresholdsValueEditor extends React.PureComponent< diff --git a/public/app/features/dimensions/editors/URLPickerTab.tsx b/public/app/features/dimensions/editors/URLPickerTab.tsx index ff2638fbd9e..5402d2e6112 100644 --- a/public/app/features/dimensions/editors/URLPickerTab.tsx +++ b/public/app/features/dimensions/editors/URLPickerTab.tsx @@ -1,11 +1,12 @@ -import React, { Dispatch, SetStateAction } from 'react'; -import { Field, Input, Label, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { Dispatch, SetStateAction } from 'react'; import SVG from 'react-inlinesvg'; -import { MediaType } from '../types'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Field, Input, Label, useStyles2 } from '@grafana/ui'; + import { getPublicOrAbsoluteUrl } from '../resource'; +import { MediaType } from '../types'; interface Props { newValue: string; diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx index f30c5364cfd..4298ee0d84d 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx @@ -1,10 +1,12 @@ -import React, { useCallback, useEffect, useRef } from 'react'; -import { GrafanaTheme2, MappingType, SpecialValueMatch, SelectableValue, ValueMappingResult } from '@grafana/data'; -import { Draggable } from 'react-beautiful-dnd'; import { css } from '@emotion/css'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { Draggable } from 'react-beautiful-dnd'; + +import { GrafanaTheme2, MappingType, SpecialValueMatch, SelectableValue, ValueMappingResult } from '@grafana/data'; import { useStyles2, Icon, Select, HorizontalGroup, ColorPicker, IconButton, Input, Button } from '@grafana/ui'; -import { ResourcePicker } from '../ResourcePicker'; + import { ResourcePickerSize, ResourceFolderName, MediaType } from '../../types'; +import { ResourcePicker } from '../ResourcePicker'; export interface ValueMappingEditRowModel { type: MappingType; diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.test.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.test.tsx index 74426b5b5a4..af45eee4069 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.test.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { ValueMappingsEditor, Props } from './ValueMappingsEditor'; +import React from 'react'; + import { MappingType } from '@grafana/data'; +import { ValueMappingsEditor, Props } from './ValueMappingsEditor'; + const setup = (spy?: any, propOverrides?: object) => { const props: Props = { onChange: (mappings: any) => { diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx index 0081440dfc6..46020d3f21d 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx @@ -1,11 +1,14 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { GrafanaTheme2, MappingType, StandardEditorProps, ValueMapping } from '@grafana/data'; import { css } from '@emotion/css'; -import { buildEditRowModels, editModelToSaveModel, ValueMappingsEditorModal } from './ValueMappingsEditorModal'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { GrafanaTheme2, MappingType, StandardEditorProps, ValueMapping } from '@grafana/data'; import { useStyles2, VerticalGroup, Icon, ColorPicker, Button, Modal } from '@grafana/ui'; + import { MediaType, ResourceFolderName, ResourcePickerSize } from '../../types'; import { ResourcePicker } from '../ResourcePicker'; +import { buildEditRowModels, editModelToSaveModel, ValueMappingsEditorModal } from './ValueMappingsEditorModal'; + export interface Props extends StandardEditorProps { showIcon?: boolean; } diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx index e5185c4da07..370e24f7f6b 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { ValueMappingsEditorModal, Props } from './ValueMappingsEditorModal'; +import React from 'react'; + import { MappingType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { selectOptionInTest } from '@grafana/ui'; +import { ValueMappingsEditorModal, Props } from './ValueMappingsEditorModal'; + const setup = (spy?: any, propOverrides?: object) => { const props: Props = { onClose: jest.fn(), diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx index a8ff61d7c30..aacb26e065a 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditorModal.tsx @@ -1,10 +1,12 @@ -import React, { useEffect, useState } from 'react'; -import { GrafanaTheme2, MappingType, SelectableValue, SpecialValueMatch, ValueMapping } from '@grafana/data'; -import { ValueMappingEditRow, ValueMappingEditRowModel } from './ValueMappingEditRow'; -import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; import { css } from '@emotion/css'; +import React, { useEffect, useState } from 'react'; +import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; + +import { GrafanaTheme2, MappingType, SelectableValue, SpecialValueMatch, ValueMapping } from '@grafana/data'; import { useStyles2, Modal, ValuePicker, Button } from '@grafana/ui'; +import { ValueMappingEditRow, ValueMappingEditRowModel } from './ValueMappingEditRow'; + export interface Props { value: ValueMapping[]; onChange: (valueMappings: ValueMapping[]) => void; diff --git a/public/app/features/dimensions/resource.ts b/public/app/features/dimensions/resource.ts index f7e0d87a1ea..d48873a5740 100644 --- a/public/app/features/dimensions/resource.ts +++ b/public/app/features/dimensions/resource.ts @@ -1,4 +1,5 @@ import { DataFrame } from '@grafana/data'; + import { DimensionSupplier, ResourceDimensionConfig, ResourceDimensionMode } from './types'; import { findField, getLastNotNullFieldValue } from './utils'; diff --git a/public/app/features/dimensions/scalar.test.ts b/public/app/features/dimensions/scalar.test.ts index 3055b2cbc59..7643d090f1f 100644 --- a/public/app/features/dimensions/scalar.test.ts +++ b/public/app/features/dimensions/scalar.test.ts @@ -1,7 +1,9 @@ import { ArrayVector, DataFrame, FieldType } from '@grafana/data'; -import { ScalarDimensionMode } from '.'; + import { getScalarDimension } from './scalar'; +import { ScalarDimensionMode } from '.'; + describe('scalar dimensions', () => { it('handles string field', () => { const values = ['-720', '10', '540', '90', '-210']; diff --git a/public/app/features/dimensions/scalar.ts b/public/app/features/dimensions/scalar.ts index f5388b61c5e..377b412e5a1 100644 --- a/public/app/features/dimensions/scalar.ts +++ b/public/app/features/dimensions/scalar.ts @@ -1,4 +1,5 @@ import { DataFrame, Field } from '@grafana/data'; + import { DimensionSupplier, ScalarDimensionConfig, ScalarDimensionMode } from './types'; import { findField, getLastNotNullFieldValue } from './utils'; diff --git a/public/app/features/dimensions/scale.test.ts b/public/app/features/dimensions/scale.test.ts index 096171babee..d10d65e5875 100644 --- a/public/app/features/dimensions/scale.test.ts +++ b/public/app/features/dimensions/scale.test.ts @@ -1,4 +1,5 @@ import { ArrayVector, DataFrame, FieldType } from '@grafana/data'; + import { getScaledDimension, validateScaleConfig } from './scale'; describe('scale dimensions', () => { diff --git a/public/app/features/dimensions/scale.ts b/public/app/features/dimensions/scale.ts index 45c82c027a1..d3b06fec454 100644 --- a/public/app/features/dimensions/scale.ts +++ b/public/app/features/dimensions/scale.ts @@ -1,9 +1,12 @@ import { DataFrame, Field } from '@grafana/data'; -import { ScaleDimensionMode } from '.'; + import { getMinMaxAndDelta } from '../../../../packages/grafana-data/src/field/scale'; + import { ScaleDimensionConfig, DimensionSupplier, ScaleDimensionOptions } from './types'; import { findField, getLastNotNullFieldValue } from './utils'; +import { ScaleDimensionMode } from '.'; + //--------------------------------------------------------- // Scale dimension //--------------------------------------------------------- diff --git a/public/app/features/dimensions/text.ts b/public/app/features/dimensions/text.ts index c77d0c3e21e..5a7db09529d 100644 --- a/public/app/features/dimensions/text.ts +++ b/public/app/features/dimensions/text.ts @@ -1,4 +1,5 @@ import { DataFrame, Field, FieldType, formattedValueToString } from '@grafana/data'; + import { DimensionSupplier, TextDimensionConfig, TextDimensionMode } from './types'; import { findField, getLastNotNullFieldValue } from './utils'; diff --git a/public/app/features/dimensions/utils.ts b/public/app/features/dimensions/utils.ts index 19bc7342d64..983356ab430 100644 --- a/public/app/features/dimensions/utils.ts +++ b/public/app/features/dimensions/utils.ts @@ -1,4 +1,5 @@ import { DataFrame, PanelData, Field, getFieldDisplayName, ReducerID } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { getColorDimension, getScaledDimension, @@ -11,7 +12,7 @@ import { TextDimensionConfig, ScalarDimensionConfig, } from 'app/features/dimensions'; -import { config } from '@grafana/runtime'; + import { getScalarDimension } from './scalar'; export function getColorDimensionFromData( diff --git a/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx b/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx index 161f6b57443..4d2366ce2b2 100644 --- a/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx +++ b/public/app/features/explore/AddToDashboard/AddToDashboardModal.tsx @@ -1,15 +1,18 @@ -import React, { useEffect, useState } from 'react'; -import { Alert, Button, Field, InputControl, Modal, RadioButtonGroup } from '@grafana/ui'; -import { locationUtil, SelectableValue } from '@grafana/data'; -import { setDashboardInLocalStorage, AddToDashboardError } from './addToDashboard'; -import { useSelector } from 'react-redux'; -import { ExploreId } from 'app/types'; -import { DashboardPicker } from 'app/core/components/Select/DashboardPicker'; -import { DeepMap, FieldError, useForm } from 'react-hook-form'; -import { config, locationService, reportInteraction } from '@grafana/runtime'; -import { getExploreItemSelector } from '../state/selectors'; import { partial } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { DeepMap, FieldError, useForm } from 'react-hook-form'; +import { useSelector } from 'react-redux'; + +import { locationUtil, SelectableValue } from '@grafana/data'; +import { config, locationService, reportInteraction } from '@grafana/runtime'; +import { Alert, Button, Field, InputControl, Modal, RadioButtonGroup } from '@grafana/ui'; +import { DashboardPicker } from 'app/core/components/Select/DashboardPicker'; import { removeDashboardToFetchFromLocalStorage } from 'app/features/dashboard/state/initDashboard'; +import { ExploreId } from 'app/types'; + +import { getExploreItemSelector } from '../state/selectors'; + +import { setDashboardInLocalStorage, AddToDashboardError } from './addToDashboard'; enum SaveTarget { NewDashboard = 'new-dashboard', @@ -18,11 +21,11 @@ enum SaveTarget { const SAVE_TARGETS: Array> = [ { - label: 'New Dashboard', + label: 'New dashboard', value: SaveTarget.NewDashboard, }, { - label: 'Existing Dashboard', + label: 'Existing dashboard', value: SaveTarget.ExistingDashboard, }, ]; @@ -141,7 +144,7 @@ export const AddToDashboardModal = ({ onClose, exploreId }: Props) => { ( - + )} @@ -195,7 +198,7 @@ export const AddToDashboardModal = ({ onClose, exploreId }: Props) => { Open in new tab diff --git a/public/app/features/explore/AddToDashboard/addToDashboard.test.ts b/public/app/features/explore/AddToDashboard/addToDashboard.test.ts index b81c7c9ca48..cc6f3c054b2 100644 --- a/public/app/features/explore/AddToDashboard/addToDashboard.test.ts +++ b/public/app/features/explore/AddToDashboard/addToDashboard.test.ts @@ -1,9 +1,11 @@ import { DataQuery, MutableDataFrame } from '@grafana/data'; -import { ExplorePanelData } from 'app/types'; -import { createEmptyQueryResponse } from '../state/utils'; -import { setDashboardInLocalStorage } from './addToDashboard'; -import * as api from 'app/features/dashboard/state/initDashboard'; import { backendSrv } from 'app/core/services/backend_srv'; +import * as api from 'app/features/dashboard/state/initDashboard'; +import { ExplorePanelData } from 'app/types'; + +import { createEmptyQueryResponse } from '../state/utils'; + +import { setDashboardInLocalStorage } from './addToDashboard'; describe('addPanelToDashboard', () => { let spy: jest.SpyInstance; diff --git a/public/app/features/explore/AddToDashboard/index.test.tsx b/public/app/features/explore/AddToDashboard/index.test.tsx index 62db02ba6b4..a4f4275ceb3 100644 --- a/public/app/features/explore/AddToDashboard/index.test.tsx +++ b/public/app/features/explore/AddToDashboard/index.test.tsx @@ -1,18 +1,22 @@ -import React, { ReactNode } from 'react'; import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React, { ReactNode } from 'react'; +import { Provider } from 'react-redux'; + +import { DataQuery } from '@grafana/data'; +import { locationService, setEchoSrv } from '@grafana/runtime'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { Echo } from 'app/core/services/echo/Echo'; +import * as initDashboard from 'app/features/dashboard/state/initDashboard'; +import { DashboardSearchItemType } from 'app/features/search/types'; import { configureStore } from 'app/store/configureStore'; import { ExploreId, ExploreState } from 'app/types'; -import { Provider } from 'react-redux'; -import { AddToDashboard } from '.'; -import * as api from './addToDashboard'; -import { locationService, setEchoSrv } from '@grafana/runtime'; -import * as initDashboard from 'app/features/dashboard/state/initDashboard'; -import { DataQuery } from '@grafana/data'; + import { createEmptyQueryResponse } from '../state/utils'; -import { backendSrv } from 'app/core/services/backend_srv'; -import { DashboardSearchItemType } from 'app/features/search/types'; -import { Echo } from 'app/core/services/echo/Echo'; + +import * as api from './addToDashboard'; + +import { AddToDashboard } from '.'; const setup = (children: ReactNode, queries: DataQuery[] = [{ refId: 'A' }]) => { const store = configureStore({ @@ -28,7 +32,7 @@ const setup = (children: ReactNode, queries: DataQuery[] = [{ refId: 'A' }]) => }; const openModal = async () => { - userEvent.click(screen.getByRole('button', { name: /add to dashboard/i })); + await userEvent.click(screen.getByRole('button', { name: /add to dashboard/i })); expect(await screen.findByRole('dialog', { name: 'Add panel to dashboard' })).toBeInTheDocument(); }; @@ -44,7 +48,7 @@ describe('AddToDashboardButton', () => { const button = await screen.findByRole('button', { name: /add to dashboard/i }); expect(button).toBeDisabled(); - userEvent.click(button); + await userEvent.click(button); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); @@ -71,7 +75,7 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('button', { name: /cancel/i })); + await userEvent.click(screen.getByRole('button', { name: /cancel/i })); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); @@ -86,7 +90,7 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('button', { name: /open$/i })); + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); await waitForAddToDashboardResponse(); @@ -105,7 +109,7 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitForAddToDashboardResponse(); @@ -124,7 +128,7 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitForAddToDashboardResponse(); @@ -138,7 +142,7 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('button', { name: /open$/i })); + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); await waitForAddToDashboardResponse(); @@ -157,7 +161,7 @@ describe('AddToDashboardButton', () => { expect(screen.queryByRole('combobox', { name: /dashboard/ })).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); expect(screen.getByRole('combobox', { name: /dashboard/ })).toBeInTheDocument(); }); @@ -168,9 +172,9 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - userEvent.click(screen.getByRole('button', { name: /open$/i })); + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); await waitForAddToDashboardResponse(); expect(locationService.push).not.toHaveBeenCalled(); @@ -203,16 +207,16 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); + await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); await waitFor(async () => { await screen.findByLabelText('Select option'); }); - userEvent.click(screen.getByLabelText('Select option')); + await userEvent.click(screen.getByLabelText('Select option')); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitFor(async () => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); @@ -246,16 +250,16 @@ describe('AddToDashboardButton', () => { await openModal(); - userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); + await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); await waitFor(async () => { await screen.findByLabelText('Select option'); }); - userEvent.click(screen.getByLabelText('Select option')); + await userEvent.click(screen.getByLabelText('Select option')); - userEvent.click(screen.getByRole('button', { name: /open$/i })); + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); await waitFor(async () => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); @@ -281,7 +285,7 @@ describe('AddToDashboardButton', () => { await openModal(); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitFor(async () => { expect(await screen.findByRole('alert')).toBeInTheDocument(); @@ -300,7 +304,7 @@ describe('AddToDashboardButton', () => { await openModal(); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitFor(async () => { expect(await screen.findByRole('alert')).toBeInTheDocument(); @@ -328,16 +332,16 @@ describe('AddToDashboardButton', () => { await openModal(); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); + await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); await waitFor(async () => { await screen.findByLabelText('Select option'); }); - userEvent.click(screen.getByLabelText('Select option')); + await userEvent.click(screen.getByLabelText('Select option')); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitFor(async () => { expect(await screen.findByRole('alert')).toBeInTheDocument(); @@ -352,7 +356,7 @@ describe('AddToDashboardButton', () => { await openModal(); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); await waitFor(async () => { expect(await screen.findByRole('alert')).toBeInTheDocument(); diff --git a/public/app/features/explore/AddToDashboard/index.tsx b/public/app/features/explore/AddToDashboard/index.tsx index 50d19c3e643..37cbd9a8697 100644 --- a/public/app/features/explore/AddToDashboard/index.tsx +++ b/public/app/features/explore/AddToDashboard/index.tsx @@ -1,10 +1,13 @@ import React, { useState } from 'react'; -import { ExploreId } from 'app/types'; -import { AddToDashboardModal } from './AddToDashboardModal'; -import { ToolbarButton } from '@grafana/ui'; import { useSelector } from 'react-redux'; + +import { ToolbarButton } from '@grafana/ui'; +import { ExploreId } from 'app/types'; + import { getExploreItemSelector } from '../state/selectors'; +import { AddToDashboardModal } from './AddToDashboardModal'; + interface Props { exploreId: ExploreId; } diff --git a/public/app/features/explore/ElapsedTime.tsx b/public/app/features/explore/ElapsedTime.tsx index c358a0048bf..f0eea87e29a 100644 --- a/public/app/features/explore/ElapsedTime.tsx +++ b/public/app/features/explore/ElapsedTime.tsx @@ -1,5 +1,6 @@ import React, { FC, useState, useEffect } from 'react'; import { useInterval } from 'react-use'; + import { Time, TimeProps } from './Time'; const INTERVAL = 150; diff --git a/public/app/features/explore/ErrorContainer.test.tsx b/public/app/features/explore/ErrorContainer.test.tsx index accc19c1877..a3699acc9d4 100644 --- a/public/app/features/explore/ErrorContainer.test.tsx +++ b/public/app/features/explore/ErrorContainer.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { ErrorContainer, ErrorContainerProps } from './ErrorContainer'; describe('ErrorContainer', () => { diff --git a/public/app/features/explore/ErrorContainer.tsx b/public/app/features/explore/ErrorContainer.tsx index fffa9c10313..9f6d6a4a320 100644 --- a/public/app/features/explore/ErrorContainer.tsx +++ b/public/app/features/explore/ErrorContainer.tsx @@ -1,4 +1,5 @@ import React, { FunctionComponent } from 'react'; + import { DataQueryError } from '@grafana/data'; import { Alert } from '@grafana/ui'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index b9ce6c9f5f7..d1c30e4d722 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; -import { DataSourceApi, LoadingState, CoreApp, createTheme } from '@grafana/data'; import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { AutoSizerProps } from 'react-virtualized-auto-sizer'; + +import { DataSourceApi, LoadingState, CoreApp, createTheme } from '@grafana/data'; +import { configureStore } from 'app/store/configureStore'; import { ExploreId } from 'app/types/explore'; + import { Explore, Props } from './Explore'; import { scanStopAction } from './state/query'; -import { Provider } from 'react-redux'; -import { configureStore } from 'app/store/configureStore'; -import { AutoSizerProps } from 'react-virtualized-auto-sizer'; import { createEmptyQueryResponse } from './state/utils'; const makeEmptyQueryResponse = (loadingState: LoadingState) => { diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 38ec9457441..319d3801189 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -1,40 +1,42 @@ -import React, { createRef } from 'react'; import { css, cx } from '@emotion/css'; -import { compose } from 'redux'; +import memoizeOne from 'memoize-one'; +import React, { createRef } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; -import memoizeOne from 'memoize-one'; +import { compose } from 'redux'; +import { Unsubscribable } from 'rxjs'; + +import { AbsoluteTimeRange, DataQuery, GrafanaTheme2, LoadingState, RawTimeRange } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Collapse, CustomScrollbar, ErrorBoundaryAlert, Themeable2, withTheme2 } from '@grafana/ui'; -import { AbsoluteTimeRange, DataQuery, GrafanaTheme2, LoadingState, RawTimeRange } from '@grafana/data'; - -import LogsContainer from './LogsContainer'; -import { QueryRows } from './QueryRows'; -import TableContainer from './TableContainer'; -import RichHistoryContainer from './RichHistory/RichHistoryContainer'; -import ExploreQueryInspector from './ExploreQueryInspector'; -import { splitOpen } from './state/main'; -import { changeSize, changeGraphStyle } from './state/explorePane'; -import { makeAbsoluteTime, updateTimeRange } from './state/time'; -import { addQueryRow, loadLogsVolumeData, modifyQueries, scanStart, scanStopAction, setQueries } from './state/query'; -import { ExploreGraphStyle, ExploreId, ExploreItemState } from 'app/types/explore'; -import { StoreState } from 'app/types'; -import { ExploreToolbar } from './ExploreToolbar'; -import { NoDataSourceCallToAction } from './NoDataSourceCallToAction'; -import { getTimeZone } from '../profile/state/selectors'; -import { SecondaryActions } from './SecondaryActions'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, FilterItem } from '@grafana/ui/src/components/Table/types'; -import { NodeGraphContainer } from './NodeGraphContainer'; -import { ResponseErrorContainer } from './ResponseErrorContainer'; -import { TraceViewContainer } from './TraceView/TraceViewContainer'; -import { ExploreGraph } from './ExploreGraph'; -import { LogsVolumePanel } from './LogsVolumePanel'; -import { ExploreGraphLabel } from './ExploreGraphLabel'; import appEvents from 'app/core/app_events'; -import { AbsoluteTimeEvent } from 'app/types/events'; -import { Unsubscribable } from 'rxjs'; import { getNodeGraphDataFrames } from 'app/plugins/panel/nodeGraph/utils'; +import { StoreState } from 'app/types'; +import { AbsoluteTimeEvent } from 'app/types/events'; +import { ExploreGraphStyle, ExploreId, ExploreItemState } from 'app/types/explore'; + +import { getTimeZone } from '../profile/state/selectors'; + +import { ExploreGraph } from './ExploreGraph'; +import { ExploreGraphLabel } from './ExploreGraphLabel'; +import ExploreQueryInspector from './ExploreQueryInspector'; +import { ExploreToolbar } from './ExploreToolbar'; +import LogsContainer from './LogsContainer'; +import { LogsVolumePanel } from './LogsVolumePanel'; import { NoData } from './NoData'; +import { NoDataSourceCallToAction } from './NoDataSourceCallToAction'; +import { NodeGraphContainer } from './NodeGraphContainer'; +import { QueryRows } from './QueryRows'; +import { ResponseErrorContainer } from './ResponseErrorContainer'; +import RichHistoryContainer from './RichHistory/RichHistoryContainer'; +import { SecondaryActions } from './SecondaryActions'; +import TableContainer from './TableContainer'; +import { TraceViewContainer } from './TraceView/TraceViewContainer'; +import { changeSize, changeGraphStyle } from './state/explorePane'; +import { splitOpen } from './state/main'; +import { addQueryRow, loadLogsVolumeData, modifyQueries, scanStart, scanStopAction, setQueries } from './state/query'; +import { makeAbsoluteTime, updateTimeRange } from './state/time'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/public/app/features/explore/ExploreActions.tsx b/public/app/features/explore/ExploreActions.tsx new file mode 100644 index 00000000000..95b038d4f0e --- /dev/null +++ b/public/app/features/explore/ExploreActions.tsx @@ -0,0 +1,96 @@ +import { useRegisterActions, useKBar, Action, Priority } from 'kbar'; +import { FC, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { ExploreId } from 'app/types'; + +import { splitOpen, splitClose } from './state/main'; +import { runQueries } from './state/query'; +import { isSplit } from './state/selectors'; + +interface Props { + exploreIdLeft: ExploreId; + exploreIdRight?: ExploreId; +} + +export const ExploreActions: FC = ({ exploreIdLeft, exploreIdRight }: Props) => { + const [actions, setActions] = useState([]); + const { query } = useKBar(); + const dispatch = useDispatch(); + const splitted = useSelector(isSplit); + + useEffect(() => { + const exploreSection = { + name: 'Explore', + priority: Priority.HIGH + 1, + }; + + const actionsArr: Action[] = []; + + if (splitted) { + actionsArr.push({ + id: 'explore/run-query-left', + name: 'Run Query (Left)', + keywords: 'query left', + perform: () => { + dispatch(runQueries(exploreIdLeft)); + }, + section: exploreSection, + }); + if (exploreIdRight) { + // we should always have the right exploreId if split + actionsArr.push({ + id: 'explore/run-query-right', + name: 'Run Query (Right)', + keywords: 'query right', + perform: () => { + dispatch(runQueries(exploreIdRight)); + }, + section: exploreSection, + }); + actionsArr.push({ + id: 'explore/split-view-close-left', + name: 'Close split view left', + keywords: 'split', + perform: () => { + dispatch(splitClose(exploreIdLeft)); + }, + section: exploreSection, + }); + actionsArr.push({ + id: 'explore/split-view-close-right', + name: 'Close split view right', + keywords: 'split', + perform: () => { + dispatch(splitClose(exploreIdRight)); + }, + section: exploreSection, + }); + } + } else { + actionsArr.push({ + id: 'explore/run-query', + name: 'Run Query', + keywords: 'query', + perform: () => { + dispatch(runQueries(exploreIdLeft)); + }, + section: exploreSection, + }); + actionsArr.push({ + id: 'explore/split-view-open', + name: 'Open split view', + keywords: 'split', + perform: () => { + dispatch(splitOpen()); + }, + section: exploreSection, + }); + } + setActions(actionsArr); + }, [exploreIdLeft, exploreIdRight, splitted, query, dispatch]); + + useRegisterActions(!query ? [] : actions, [actions, query]); + + return null; +}; diff --git a/public/app/features/explore/ExploreDrawer.tsx b/public/app/features/explore/ExploreDrawer.tsx index 222feef2dac..10fa3489d5e 100644 --- a/public/app/features/explore/ExploreDrawer.tsx +++ b/public/app/features/explore/ExploreDrawer.tsx @@ -1,17 +1,17 @@ // Libraries -import React from 'react'; -import { Resizable, ResizeCallback } from 're-resizable'; import { css, cx, keyframes } from '@emotion/css'; +import { Resizable, ResizeCallback } from 're-resizable'; +import React from 'react'; // Services & Utils +import { GrafanaTheme2 } from '@grafana/data'; import { stylesFactory, useTheme2 } from '@grafana/ui'; // Types -import { GrafanaTheme2 } from '@grafana/data'; -const drawerSlide = keyframes` +const drawerSlide = (theme: GrafanaTheme2) => keyframes` 0% { - transform: translateY(400px); + transform: translateY(${theme.components.horizontalDrawer.defaultHeight}px); } 100% { @@ -32,7 +32,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => { `, drawerActive: css` opacity: 1; - animation: 0.5s ease-out ${drawerSlide}; + animation: 0.5s ease-out ${drawerSlide(theme)}; `, rzHandle: css` background: ${theme.colors.secondary.main}; @@ -66,7 +66,7 @@ export function ExploreDrawer(props: Props) { return ( > = EXPLORE_GRAPH_STYLES.map((style) => ({ diff --git a/public/app/features/explore/ExplorePaneContainer.tsx b/public/app/features/explore/ExplorePaneContainer.tsx index e46b3c5ceea..83dd8bb028b 100644 --- a/public/app/features/explore/ExplorePaneContainer.tsx +++ b/public/app/features/explore/ExplorePaneContainer.tsx @@ -1,13 +1,10 @@ +import memoizeOne from 'memoize-one'; import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import memoizeOne from 'memoize-one'; + import { DataQuery, ExploreUrlState, EventBusExtended, EventBusSrv } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import store from 'app/core/store'; -import { lastSavedUrl, cleanupPaneAction } from './state/main'; -import { initializeExplore, refreshExplore } from './state/explorePane'; -import { ExploreId } from 'app/types/explore'; -import { StoreState } from 'app/types'; import { DEFAULT_RANGE, ensureQueries, @@ -16,8 +13,14 @@ import { lastUsedDatasourceKeyForOrgId, parseUrlState, } from 'app/core/utils/explore'; +import { StoreState } from 'app/types'; +import { ExploreId } from 'app/types/explore'; + import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors'; + import Explore from './Explore'; +import { initializeExplore, refreshExplore } from './state/explorePane'; +import { lastSavedUrl, cleanupPaneAction } from './state/main'; interface OwnProps { exploreId: ExploreId; diff --git a/public/app/features/explore/ExploreQueryInspector.test.tsx b/public/app/features/explore/ExploreQueryInspector.test.tsx index f1c39254492..49af7ff1a67 100644 --- a/public/app/features/explore/ExploreQueryInspector.test.tsx +++ b/public/app/features/explore/ExploreQueryInspector.test.tsx @@ -1,8 +1,10 @@ +import { render, screen, fireEvent } from '@testing-library/react'; import React, { ComponentProps } from 'react'; import { Observable } from 'rxjs'; -import { render, screen, fireEvent } from '@testing-library/react'; + import { TimeRange, LoadingState, InternalTimeZones } from '@grafana/data'; import { ExploreId } from 'app/types'; + import { ExploreQueryInspector } from './ExploreQueryInspector'; type ExploreQueryInspectorProps = ComponentProps; diff --git a/public/app/features/explore/ExploreQueryInspector.tsx b/public/app/features/explore/ExploreQueryInspector.tsx index bda8f197cf8..f231bd9a33d 100644 --- a/public/app/features/explore/ExploreQueryInspector.tsx +++ b/public/app/features/explore/ExploreQueryInspector.tsx @@ -1,15 +1,17 @@ import React from 'react'; -import { TabbedContainer, TabConfig } from '@grafana/ui'; -import { TimeZone } from '@grafana/data'; -import { runQueries } from './state/query'; -import { StoreState, ExploreItemState, ExploreId } from 'app/types'; import { connect, ConnectedProps } from 'react-redux'; + +import { TimeZone } from '@grafana/data'; +import { TabbedContainer, TabConfig } from '@grafana/ui'; import { ExploreDrawer } from 'app/features/explore/ExploreDrawer'; -import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab'; -import { QueryInspector } from 'app/features/inspector/QueryInspector'; -import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab'; import { InspectDataTab } from 'app/features/inspector/InspectDataTab'; import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab'; +import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab'; +import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab'; +import { QueryInspector } from 'app/features/inspector/QueryInspector'; +import { StoreState, ExploreItemState, ExploreId } from 'app/types'; + +import { runQueries } from './state/query'; interface DispatchProps { width: number; @@ -71,7 +73,7 @@ export function ExploreQueryInspector(props: Props) { tabs.push(errorTab); } return ( - {}}> + ); diff --git a/public/app/features/explore/ExploreTimeControls.tsx b/public/app/features/explore/ExploreTimeControls.tsx index ed8b5a2ba66..aa32dbde156 100644 --- a/public/app/features/explore/ExploreTimeControls.tsx +++ b/public/app/features/explore/ExploreTimeControls.tsx @@ -1,18 +1,11 @@ -// Libaries import React, { Component } from 'react'; -// Types -import { ExploreId } from 'app/types'; import { TimeRange, TimeZone, RawTimeRange, dateTimeForTimeZone, dateMath } from '@grafana/data'; - -// State - -// Components -import { TimeSyncButton } from './TimeSyncButton'; import { TimePickerWithHistory } from 'app/core/components/TimePicker/TimePickerWithHistory'; - -// Utils & Services import { getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker'; +import { ExploreId } from 'app/types'; + +import { TimeSyncButton } from './TimeSyncButton'; export interface Props { exploreId: ExploreId; @@ -92,6 +85,7 @@ export class ExploreTimeControls extends Component { {...timePickerCommonProps} timeSyncButton={timeSyncButton} isSynced={syncedTimes} + widthOverride={splitted ? window.innerWidth / 2 : undefined} onChange={this.onChangeTimePicker} onChangeTimeZone={onChangeTimeZone} onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth} diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index af8030b01e9..aace8efe1aa 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -1,23 +1,26 @@ import React, { lazy, PureComponent, RefObject, Suspense } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { ExploreId } from 'app/types/explore'; -import { PageToolbar, SetInterval, ToolbarButton, ToolbarButtonRow } from '@grafana/ui'; + import { DataSourceInstanceSettings, RawTimeRange } from '@grafana/data'; import { config, DataSourcePicker } from '@grafana/runtime'; -import { StoreState } from 'app/types/store'; +import { PageToolbar, SetInterval, ToolbarButton, ToolbarButtonRow } from '@grafana/ui'; import { createAndCopyShortLink } from 'app/core/utils/shortLinks'; -import { changeDatasource } from './state/datasource'; -import { splitClose, splitOpen } from './state/main'; -import { syncTimes, changeRefreshInterval } from './state/time'; -import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors'; +import { ExploreId } from 'app/types/explore'; +import { StoreState } from 'app/types/store'; + +import { DashNavButton } from '../dashboard/components/DashNav/DashNavButton'; import { updateFiscalYearStartMonthForSession, updateTimeZoneForSession } from '../profile/state/reducers'; +import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors'; + import { ExploreTimeControls } from './ExploreTimeControls'; import { LiveTailButton } from './LiveTailButton'; import { RunButton } from './RunButton'; -import { LiveTailControls } from './useLiveTailControls'; +import { changeDatasource } from './state/datasource'; +import { splitClose, splitOpen } from './state/main'; import { cancelQueries, runQueries } from './state/query'; import { isSplit } from './state/selectors'; -import { DashNavButton } from '../dashboard/components/DashNav/DashNavButton'; +import { syncTimes, changeRefreshInterval } from './state/time'; +import { LiveTailControls } from './useLiveTailControls'; const AddToDashboard = lazy(() => import('./AddToDashboard').then(({ AddToDashboard }) => ({ default: AddToDashboard })) diff --git a/public/app/features/explore/FeatureTogglePage.tsx b/public/app/features/explore/FeatureTogglePage.tsx new file mode 100644 index 00000000000..54a00691aaa --- /dev/null +++ b/public/app/features/explore/FeatureTogglePage.tsx @@ -0,0 +1,31 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; + +export default function FeatureTogglePage() { + const styles = useStyles2( + (theme: GrafanaTheme2) => + css` + margin-top: ${theme.spacing(2)}; + ` + ); + + return ( + + +

Explore is disabled

+ To enable Explore, enable it in the Grafana config: +
+
+            {`[explore]
+enable = true
+`}
+          
+
+
+
+ ); +} diff --git a/public/app/features/explore/LiveLogs.test.tsx b/public/app/features/explore/LiveLogs.test.tsx index 7690d59f54b..901a0f0a9c7 100644 --- a/public/app/features/explore/LiveLogs.test.tsx +++ b/public/app/features/explore/LiveLogs.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; -import { LogLevel, LogRowModel, MutableDataFrame } from '@grafana/data'; import { mount } from 'enzyme'; +import React from 'react'; + +import { LogLevel, LogRowModel, MutableDataFrame } from '@grafana/data'; + import { LiveLogsWithTheme } from './LiveLogs'; describe('LiveLogs', () => { diff --git a/public/app/features/explore/LiveLogs.tsx b/public/app/features/explore/LiveLogs.tsx index b3e9f832d8f..bf07dd72aeb 100644 --- a/public/app/features/explore/LiveLogs.tsx +++ b/public/app/features/explore/LiveLogs.tsx @@ -1,9 +1,9 @@ -import React, { PureComponent } from 'react'; import { css, cx } from '@emotion/css'; +import React, { PureComponent } from 'react'; import tinycolor from 'tinycolor2'; -import { LogMessageAnsi, getLogRowStyles, Icon, Button, Themeable2, withTheme2 } from '@grafana/ui'; import { LogRowModel, TimeZone, dateTimeFormat, GrafanaTheme2 } from '@grafana/data'; +import { LogMessageAnsi, getLogRowStyles, Icon, Button, Themeable2, withTheme2 } from '@grafana/ui'; import { ElapsedTime } from './ElapsedTime'; diff --git a/public/app/features/explore/LiveTailButton.tsx b/public/app/features/explore/LiveTailButton.tsx index 6bdc8335510..304e7aad342 100644 --- a/public/app/features/explore/LiveTailButton.tsx +++ b/public/app/features/explore/LiveTailButton.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; import { CSSTransition } from 'react-transition-group'; + import { Tooltip, ButtonGroup, ToolbarButton } from '@grafana/ui'; type LiveTailButtonProps = { diff --git a/public/app/features/explore/Logs.test.tsx b/public/app/features/explore/Logs.test.tsx index 82d32ffbb29..c63c4e909a2 100644 --- a/public/app/features/explore/Logs.test.tsx +++ b/public/app/features/explore/Logs.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; +import React from 'react'; + import { LoadingState, LogLevel, LogRowModel, MutableDataFrame, toUtc } from '@grafana/data'; import { Logs } from './Logs'; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 1d6d28b051d..6e7f1ef8e39 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -1,8 +1,8 @@ -import React, { PureComponent, createRef } from 'react'; import { css } from '@emotion/css'; import { capitalize } from 'lodash'; import memoizeOne from 'memoize-one'; -import { TooltipDisplayMode } from '@grafana/schema'; +import React, { PureComponent, createRef } from 'react'; + import { rangeUtil, RawTimeRange, @@ -21,6 +21,7 @@ import { GrafanaTheme2, LoadingState, } from '@grafana/data'; +import { TooltipDisplayMode } from '@grafana/schema'; import { RadioButtonGroup, LogRows, @@ -31,12 +32,13 @@ import { withTheme2, Themeable2, } from '@grafana/ui'; -import store from 'app/core/store'; +import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; import { dedupLogRows, filterLogLevels } from 'app/core/logs_model'; +import store from 'app/core/store'; + +import { ExploreGraph } from './ExploreGraph'; import { LogsMetaRow } from './LogsMetaRow'; import LogsNavigation from './LogsNavigation'; -import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; -import { ExploreGraph } from './ExploreGraph'; const SETTINGS_KEYS = { showLabels: 'grafana.explore.logs.showLabels', diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index c96caa86c79..fb27a793e0d 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -1,7 +1,7 @@ +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { css } from '@emotion/css'; -import { Collapse } from '@grafana/ui'; + import { AbsoluteTimeRange, Field, @@ -10,16 +10,19 @@ import { LogRowModel, RawTimeRange, } from '@grafana/data'; -import { ExploreId, ExploreItemState } from 'app/types/explore'; +import { Collapse } from '@grafana/ui'; import { StoreState } from 'app/types'; +import { ExploreId, ExploreItemState } from 'app/types/explore'; + +import { getTimeZone } from '../profile/state/selectors'; + +import { LiveLogsWithTheme } from './LiveLogs'; +import { Logs } from './Logs'; import { splitOpen } from './state/main'; import { addResultsToCache, clearCache } from './state/query'; import { updateTimeRange } from './state/time'; -import { getTimeZone } from '../profile/state/selectors'; -import { LiveLogsWithTheme } from './LiveLogs'; -import { Logs } from './Logs'; -import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition'; import { LiveTailControls } from './useLiveTailControls'; +import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition'; import { getFieldLinksForExplore } from './utils/links'; interface LogsContainerProps extends PropsFromRedux { diff --git a/public/app/features/explore/LogsMetaRow.tsx b/public/app/features/explore/LogsMetaRow.tsx index c7841420117..3f256ef0db1 100644 --- a/public/app/features/explore/LogsMetaRow.tsx +++ b/public/app/features/explore/LogsMetaRow.tsx @@ -1,7 +1,9 @@ import React from 'react'; + import { LogsDedupStrategy, LogsMetaItem, LogsMetaKind, LogRowModel } from '@grafana/data'; import { Button, Tooltip, Icon, LogLabels } from '@grafana/ui'; import { MAX_CHARACTERS } from '@grafana/ui/src/components/Logs/LogRowMessage'; + import { MetaInfoText, MetaItemProps } from './MetaInfoText'; export type Props = { @@ -58,7 +60,7 @@ export const LogsMetaRow: React.FC = React.memo( label: '', value: ( ), } diff --git a/public/app/features/explore/LogsNavigation.test.tsx b/public/app/features/explore/LogsNavigation.test.tsx index 1c78947acb2..b41f45ab25e 100644 --- a/public/app/features/explore/LogsNavigation.test.tsx +++ b/public/app/features/explore/LogsNavigation.test.tsx @@ -1,6 +1,8 @@ -import React, { ComponentProps } from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; +import React, { ComponentProps } from 'react'; + import { LogsSortOrder } from '@grafana/data'; + import LogsNavigation from './LogsNavigation'; type LogsNavigationProps = ComponentProps; diff --git a/public/app/features/explore/LogsNavigation.tsx b/public/app/features/explore/LogsNavigation.tsx index 73dd0a540d9..5c2cd5a0904 100644 --- a/public/app/features/explore/LogsNavigation.tsx +++ b/public/app/features/explore/LogsNavigation.tsx @@ -1,8 +1,10 @@ -import React, { memo, useState, useEffect, useRef } from 'react'; -import { isEqual } from 'lodash'; import { css } from '@emotion/css'; +import { isEqual } from 'lodash'; +import React, { memo, useState, useEffect, useRef } from 'react'; + import { LogsSortOrder, AbsoluteTimeRange, TimeZone, DataQuery, GrafanaTheme2 } from '@grafana/data'; import { Button, Icon, Spinner, useTheme2 } from '@grafana/ui'; + import { LogsNavigationPages } from './LogsNavigationPages'; type Props = { diff --git a/public/app/features/explore/LogsNavigationPages.test.tsx b/public/app/features/explore/LogsNavigationPages.test.tsx index 9b1b7790326..6320bb1d78b 100644 --- a/public/app/features/explore/LogsNavigationPages.test.tsx +++ b/public/app/features/explore/LogsNavigationPages.test.tsx @@ -1,5 +1,6 @@ -import React, { ComponentProps } from 'react'; import { render, screen } from '@testing-library/react'; +import React, { ComponentProps } from 'react'; + import { LogsNavigationPages } from './LogsNavigationPages'; type LogsNavigationPagesProps = ComponentProps; diff --git a/public/app/features/explore/LogsNavigationPages.tsx b/public/app/features/explore/LogsNavigationPages.tsx index 16f92cb9d47..a5c978b0d8c 100644 --- a/public/app/features/explore/LogsNavigationPages.tsx +++ b/public/app/features/explore/LogsNavigationPages.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { dateTimeFormat, systemDateFormats, TimeZone, AbsoluteTimeRange, GrafanaTheme2 } from '@grafana/data'; import { CustomScrollbar, Spinner, useTheme2 } from '@grafana/ui'; + import { LogsPage } from './LogsNavigation'; type Props = { diff --git a/public/app/features/explore/LogsVolumePanel.test.tsx b/public/app/features/explore/LogsVolumePanel.test.tsx index 251097f2221..b42b3c37d1b 100644 --- a/public/app/features/explore/LogsVolumePanel.test.tsx +++ b/public/app/features/explore/LogsVolumePanel.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { LogsVolumePanel } from './LogsVolumePanel'; +import React from 'react'; + import { DataQueryResponse, LoadingState } from '@grafana/data'; +import { LogsVolumePanel } from './LogsVolumePanel'; + jest.mock('./ExploreGraph', () => { const ExploreGraph = () => ExploreGraph; return { diff --git a/public/app/features/explore/LogsVolumePanel.tsx b/public/app/features/explore/LogsVolumePanel.tsx index 806e0c7dbca..08e6fb47039 100644 --- a/public/app/features/explore/LogsVolumePanel.tsx +++ b/public/app/features/explore/LogsVolumePanel.tsx @@ -1,8 +1,10 @@ +import { css } from '@emotion/css'; +import React, { useState } from 'react'; + import { AbsoluteTimeRange, DataQueryError, DataQueryResponse, LoadingState, SplitOpen, TimeZone } from '@grafana/data'; import { Alert, Button, Collapse, InlineField, TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui'; + import { ExploreGraph } from './ExploreGraph'; -import React, { useState } from 'react'; -import { css } from '@emotion/css'; type Props = { logsVolumeData?: DataQueryResponse; diff --git a/public/app/features/explore/MetaInfoText.test.tsx b/public/app/features/explore/MetaInfoText.test.tsx index c1d7e316eef..53edd865cd1 100644 --- a/public/app/features/explore/MetaInfoText.test.tsx +++ b/public/app/features/explore/MetaInfoText.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { MetaInfoText, MetaItemProps } from './MetaInfoText'; describe('MetaInfoText', () => { it('should render component and items', () => { diff --git a/public/app/features/explore/MetaInfoText.tsx b/public/app/features/explore/MetaInfoText.tsx index dbdd234e1b4..d57baadd88f 100644 --- a/public/app/features/explore/MetaInfoText.tsx +++ b/public/app/features/explore/MetaInfoText.tsx @@ -1,5 +1,6 @@ -import React, { memo } from 'react'; import { css } from '@emotion/css'; +import React, { memo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; diff --git a/public/app/features/explore/NoData.tsx b/public/app/features/explore/NoData.tsx index d50330747f6..791505c6a57 100644 --- a/public/app/features/explore/NoData.tsx +++ b/public/app/features/explore/NoData.tsx @@ -1,9 +1,8 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; - -import { useStyles2 } from '@grafana/ui'; +import React from 'react'; import { GrafanaTheme2 } from '@grafana/data/src'; +import { useStyles2 } from '@grafana/ui'; export const NoData = () => { const css = useStyles2(getStyles); diff --git a/public/app/features/explore/NoDataSourceCallToAction.tsx b/public/app/features/explore/NoDataSourceCallToAction.tsx index d2d61d3936f..f898ec5808b 100644 --- a/public/app/features/explore/NoDataSourceCallToAction.tsx +++ b/public/app/features/explore/NoDataSourceCallToAction.tsx @@ -1,7 +1,7 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { LinkButton, CallToActionCard, Icon, useTheme2 } from '@grafana/ui'; +import React from 'react'; +import { LinkButton, CallToActionCard, Icon, useTheme2 } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/explore/NodeGraphContainer.test.tsx b/public/app/features/explore/NodeGraphContainer.test.tsx index 2560771fd8d..f05619fe848 100644 --- a/public/app/features/explore/NodeGraphContainer.test.tsx +++ b/public/app/features/explore/NodeGraphContainer.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { UnconnectedNodeGraphContainer } from './NodeGraphContainer'; +import React from 'react'; + import { getDefaultTimeRange, MutableDataFrame } from '@grafana/data'; + import { ExploreId } from '../../types'; +import { UnconnectedNodeGraphContainer } from './NodeGraphContainer'; + describe('NodeGraphContainer', () => { it('is collapsed if shown with traces', () => { const { container } = render( diff --git a/public/app/features/explore/NodeGraphContainer.tsx b/public/app/features/explore/NodeGraphContainer.tsx index e98a2edc346..65d17d11555 100644 --- a/public/app/features/explore/NodeGraphContainer.tsx +++ b/public/app/features/explore/NodeGraphContainer.tsx @@ -1,14 +1,17 @@ -import React from 'react'; -import { useToggle } from 'react-use'; -import { Badge, Collapse, useStyles2, useTheme2 } from '@grafana/ui'; -import { applyFieldOverrides, DataFrame, GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; -import { ExploreId, StoreState } from '../../types'; -import { splitOpen } from './state/main'; +import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { useLinks } from './utils/links'; +import { useToggle } from 'react-use'; + +import { applyFieldOverrides, DataFrame, GrafanaTheme2 } from '@grafana/data'; +import { Badge, Collapse, useStyles2, useTheme2 } from '@grafana/ui'; + import { NodeGraph } from '../../plugins/panel/nodeGraph'; import { useCategorizeFrames } from '../../plugins/panel/nodeGraph/useCategorizeFrames'; +import { ExploreId, StoreState } from '../../types'; + +import { splitOpen } from './state/main'; +import { useLinks } from './utils/links'; const getStyles = (theme: GrafanaTheme2) => ({ warningText: css` diff --git a/public/app/features/explore/QueryRows.test.tsx b/public/app/features/explore/QueryRows.test.tsx index 52459dc942a..3c1b791994d 100644 --- a/public/app/features/explore/QueryRows.test.tsx +++ b/public/app/features/explore/QueryRows.test.tsx @@ -1,13 +1,16 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { configureStore } from 'app/store/configureStore'; +import React from 'react'; import { Provider } from 'react-redux'; -import { QueryRows } from './QueryRows'; -import { ExploreId, ExploreState } from 'app/types'; -import { makeExplorePaneState } from './state/utils'; + import { setDataSourceSrv } from '@grafana/runtime'; -import { UserState } from '../profile/state/reducers'; +import { configureStore } from 'app/store/configureStore'; +import { ExploreId, ExploreState } from 'app/types'; + import { DataQuery } from '../../../../packages/grafana-data/src'; +import { UserState } from '../profile/state/reducers'; + +import { QueryRows } from './QueryRows'; +import { makeExplorePaneState } from './state/utils'; function setup(queries: DataQuery[]) { const defaultDs = { diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 82cec7ff84e..b8453016478 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -1,12 +1,15 @@ +import { createSelector } from '@reduxjs/toolkit'; import React, { useCallback, useMemo } from 'react'; -import { ExploreId } from 'app/types/explore'; import { useDispatch, useSelector } from 'react-redux'; -import { getDatasourceSrv } from '../plugins/datasource_srv'; -import { runQueries, changeQueriesAction } from './state/query'; + import { CoreApp, DataQuery } from '@grafana/data'; import { getNextRefIdChar } from 'app/core/utils/query'; +import { ExploreId } from 'app/types/explore'; + +import { getDatasourceSrv } from '../plugins/datasource_srv'; import { QueryEditorRows } from '../query/components/QueryEditorRows'; -import { createSelector } from '@reduxjs/toolkit'; + +import { runQueries, changeQueriesAction } from './state/query'; import { getExploreItemSelector } from './state/selectors'; interface Props { diff --git a/public/app/features/explore/ResponseErrorContainer.test.tsx b/public/app/features/explore/ResponseErrorContainer.test.tsx index 387dc9ee937..b59782f4bb3 100644 --- a/public/app/features/explore/ResponseErrorContainer.test.tsx +++ b/public/app/features/explore/ResponseErrorContainer.test.tsx @@ -1,13 +1,15 @@ +import { render, screen } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; -import { render, screen } from '@testing-library/react'; + import { DataQueryError, LoadingState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { configureStore } from '../../store/configureStore'; -import { ResponseErrorContainer } from './ResponseErrorContainer'; import { ExploreId } from '../../types'; +import { ResponseErrorContainer } from './ResponseErrorContainer'; + describe('ResponseErrorContainer', () => { it('shows error message if it does not contain refId', async () => { const errorMessage = 'test error'; diff --git a/public/app/features/explore/ResponseErrorContainer.tsx b/public/app/features/explore/ResponseErrorContainer.tsx index 5dd5f59f154..667187d2600 100644 --- a/public/app/features/explore/ResponseErrorContainer.tsx +++ b/public/app/features/explore/ResponseErrorContainer.tsx @@ -1,7 +1,10 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { ExploreId, StoreState } from '../../types'; + import { LoadingState } from '@grafana/data'; + +import { ExploreId, StoreState } from '../../types'; + import { ErrorContainer } from './ErrorContainer'; interface Props { diff --git a/public/app/features/explore/RichHistory/RichHistory.test.tsx b/public/app/features/explore/RichHistory/RichHistory.test.tsx index b588e6f3362..7ca79656a8c 100644 --- a/public/app/features/explore/RichHistory/RichHistory.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { ExploreId } from '../../../types/explore'; -import { RichHistory, RichHistoryProps, Tabs } from './RichHistory'; + import { SortOrder } from '../../../core/utils/richHistoryTypes'; +import { ExploreId } from '../../../types/explore'; + +import { RichHistory, RichHistoryProps, Tabs } from './RichHistory'; jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index 893a17ca796..3a72d1401bc 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -1,19 +1,16 @@ import React, { PureComponent } from 'react'; -//Services & Utils -import { SortOrder } from 'app/core/utils/richHistory'; -import { Themeable, withTheme, TabbedContainer, TabConfig } from '@grafana/ui'; - -//Types -import { RichHistoryQuery, ExploreId } from 'app/types/explore'; import { SelectableValue } from '@grafana/data'; +import { Themeable, withTheme, TabbedContainer, TabConfig } from '@grafana/ui'; +import { SortOrder } from 'app/core/utils/richHistory'; +import { RichHistoryQuery, ExploreId } from 'app/types/explore'; -//Components -import { RichHistorySettingsTab } from './RichHistorySettingsTab'; -import { RichHistoryQueriesTab } from './RichHistoryQueriesTab'; -import { RichHistoryStarredTab } from './RichHistoryStarredTab'; import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/utils/richHistoryTypes'; +import { RichHistoryQueriesTab } from './RichHistoryQueriesTab'; +import { RichHistorySettingsTab } from './RichHistorySettingsTab'; +import { RichHistoryStarredTab } from './RichHistoryStarredTab'; + export enum Tabs { RichHistory = 'Query history', Starred = 'Starred', diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx index 6b2a36efb64..83b73403dc8 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { render, screen, fireEvent, getByText } from '@testing-library/react'; -import { RichHistoryCard, Props } from './RichHistoryCard'; -import { ExploreId, RichHistoryQuery } from 'app/types/explore'; +import React from 'react'; + import { DataSourceApi, DataQuery } from '@grafana/data'; +import appEvents from 'app/core/app_events'; import { mockDataSource } from 'app/features/alerting/unified/mocks'; import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; import { ShowConfirmModalEvent } from 'app/types/events'; -import appEvents from 'app/core/app_events'; +import { ExploreId, RichHistoryQuery } from 'app/types/explore'; + +import { RichHistoryCard, Props } from './RichHistoryCard'; const starRichHistoryMock = jest.fn(); const deleteRichHistoryMock = jest.fn(); diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.tsx index 61120641229..850c46f9ee8 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.tsx @@ -1,23 +1,24 @@ +import { css, cx } from '@emotion/css'; import React, { useState, useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { css, cx } from '@emotion/css'; -import { stylesFactory, useTheme, TextArea, Button, IconButton } from '@grafana/ui'; -import { getDataSourceSrv } from '@grafana/runtime'; + import { GrafanaTheme, DataSourceApi, DataQuery } from '@grafana/data'; -import { RichHistoryQuery, ExploreId } from 'app/types/explore'; +import { getDataSourceSrv } from '@grafana/runtime'; +import { stylesFactory, useTheme, TextArea, Button, IconButton } from '@grafana/ui'; +import { notifyApp } from 'app/core/actions'; +import appEvents from 'app/core/app_events'; +import { createSuccessNotification } from 'app/core/copy/appNotification'; +import { copyStringToClipboard } from 'app/core/utils/explore'; import { createUrlFromRichHistory, createQueryText } from 'app/core/utils/richHistory'; import { createAndCopyShortLink } from 'app/core/utils/shortLinks'; -import { copyStringToClipboard } from 'app/core/utils/explore'; -import appEvents from 'app/core/app_events'; import { dispatch } from 'app/store/store'; -import { notifyApp } from 'app/core/actions'; -import { createSuccessNotification } from 'app/core/copy/appNotification'; import { StoreState } from 'app/types'; +import { RichHistoryQuery, ExploreId } from 'app/types/explore'; -import { starHistoryItem, commentHistoryItem, deleteHistoryItem } from '../state/history'; -import { changeDatasource } from '../state/datasource'; -import { setQueries } from '../state/query'; import { ShowConfirmModalEvent } from '../../../types/events'; +import { changeDatasource } from '../state/datasource'; +import { starHistoryItem, commentHistoryItem, deleteHistoryItem } from '../state/history'; +import { setQueries } from '../state/query'; function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreId }) { const explore = state.explore; diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx index 59533a30ac1..bdd07b4d6a5 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.test.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { render } from '@testing-library/react'; +import React from 'react'; -import { ExploreId } from '../../../types/explore'; -import { RichHistoryContainer, Props } from './RichHistoryContainer'; -import { Tabs } from './RichHistory'; import { SortOrder } from '../../../core/utils/richHistoryTypes'; +import { ExploreId } from '../../../types/explore'; + +import { Tabs } from './RichHistory'; +import { RichHistoryContainer, Props } from './RichHistoryContainer'; jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx index 93f03298c10..8f47865710d 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx @@ -1,22 +1,24 @@ // Libraries import React, { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import { useTheme2 } from '@grafana/ui'; // Types import { ExploreItemState, StoreState } from 'app/types'; import { ExploreId } from 'app/types/explore'; // Components, enums -import { RichHistory, Tabs } from './RichHistory'; - -//Actions +import { ExploreDrawer } from '../ExploreDrawer'; import { deleteRichHistory, initRichHistory, updateHistorySettings, updateHistorySearchFilters, } from '../state/history'; -import { ExploreDrawer } from '../ExploreDrawer'; + +import { RichHistory, Tabs } from './RichHistory'; + +//Actions function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreId }) { const explore = state.explore; @@ -53,7 +55,8 @@ interface OwnProps { export type Props = ConnectedProps & OwnProps; export function RichHistoryContainer(props: Props) { - const [height, setHeight] = useState(400); + const theme = useTheme2(); + const [height, setHeight] = useState(theme.components.horizontalDrawer.defaultHeight); const { richHistory, diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx index aa410be7004..bf139dceb12 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx @@ -1,14 +1,10 @@ -import React, { useState, useEffect } from 'react'; import { css } from '@emotion/css'; import { uniqBy } from 'lodash'; +import React, { useState, useEffect } from 'react'; +import { useDebounce } from 'react-use'; -// Types -import { RichHistoryQuery, ExploreId } from 'app/types/explore'; - -// Utils -import { stylesFactory, useTheme, RangeSlider, MultiSelect, Select, FilterInput } from '@grafana/ui'; import { GrafanaTheme, SelectableValue } from '@grafana/data'; - +import { stylesFactory, useTheme, RangeSlider, MultiSelect, Select, FilterInput } from '@grafana/ui'; import { SortOrder, mapNumbertoTimeInSlider, @@ -16,11 +12,10 @@ import { createDatasourcesList, filterAndSortQueries, } from 'app/core/utils/richHistory'; +import { RichHistoryQuery, ExploreId } from 'app/types/explore'; -// Components -import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; -import { useDebounce } from 'react-use'; +import RichHistoryCard from './RichHistoryCard'; export interface Props { queries: RichHistoryQuery[]; diff --git a/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx b/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx index fe6780cb286..3d69a6beb8d 100644 --- a/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistorySettingsTab.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { RichHistorySettingsTab, RichHistorySettingsProps } from './RichHistorySettingsTab'; const setup = (propOverrides?: Partial) => { diff --git a/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx b/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx index e482dfadc74..e5d76d64de1 100644 --- a/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx @@ -1,13 +1,15 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { stylesFactory, useTheme, Select, Button, Field, InlineField, InlineSwitch } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme, SelectableValue } from '@grafana/data'; -import appEvents from 'app/core/app_events'; -import { ShowConfirmModalEvent } from '../../../types/events'; -import { dispatch } from 'app/store/store'; +import { stylesFactory, useTheme, Select, Button, Field, InlineField, InlineSwitch } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; +import appEvents from 'app/core/app_events'; import { createSuccessNotification } from 'app/core/copy/appNotification'; import { MAX_HISTORY_ITEMS } from 'app/core/history/RichHistoryLocalStorage'; +import { dispatch } from 'app/store/store'; + +import { ShowConfirmModalEvent } from '../../../types/events'; export interface RichHistorySettingsProps { retentionPeriod: number; diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx index fa40adb5a43..f5ea74e9239 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { ExploreId } from '../../../types/explore'; +import React from 'react'; + import { SortOrder } from 'app/core/utils/richHistory'; + +import { ExploreId } from '../../../types/explore'; + import { RichHistoryStarredTab, Props } from './RichHistoryStarredTab'; jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx index cbe7915f791..b7ba7ffebbc 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx @@ -1,19 +1,15 @@ -import React, { useState, useEffect } from 'react'; import { css } from '@emotion/css'; import { uniqBy } from 'lodash'; +import React, { useState, useEffect } from 'react'; +import { useDebounce } from 'react-use'; -// Types +import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { stylesFactory, useTheme, Select, MultiSelect, FilterInput } from '@grafana/ui'; +import { filterAndSortQueries, createDatasourcesList, SortOrder } from 'app/core/utils/richHistory'; import { RichHistoryQuery, ExploreId } from 'app/types/explore'; -// Utils -import { stylesFactory, useTheme, Select, MultiSelect, FilterInput } from '@grafana/ui'; -import { GrafanaTheme, SelectableValue } from '@grafana/data'; -import { filterAndSortQueries, createDatasourcesList, SortOrder } from 'app/core/utils/richHistory'; - -// Components -import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; -import { useDebounce } from 'react-use'; +import RichHistoryCard from './RichHistoryCard'; export interface Props { queries: RichHistoryQuery[]; diff --git a/public/app/features/explore/RunButton.test.tsx b/public/app/features/explore/RunButton.test.tsx index dc0c3190d23..1edfe706ee3 100644 --- a/public/app/features/explore/RunButton.test.tsx +++ b/public/app/features/explore/RunButton.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import { RunButton, Props } from './RunButton'; -import { RefreshPicker } from '@grafana/ui'; import { shallow } from 'enzyme'; +import React from 'react'; + +import { RefreshPicker } from '@grafana/ui'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { RunButton, Props } from './RunButton'; + const setup = (propOverrides?: object) => { const props: Props = { isSmall: false, diff --git a/public/app/features/explore/RunButton.tsx b/public/app/features/explore/RunButton.tsx index 15307463d07..96a48506027 100644 --- a/public/app/features/explore/RunButton.tsx +++ b/public/app/features/explore/RunButton.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { RefreshPicker, defaultIntervals } from '@grafana/ui'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; diff --git a/public/app/features/explore/SecondaryActions.test.tsx b/public/app/features/explore/SecondaryActions.test.tsx index 6b55113f6d8..fb9c32e34c9 100644 --- a/public/app/features/explore/SecondaryActions.test.tsx +++ b/public/app/features/explore/SecondaryActions.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { noop } from 'lodash'; import { shallow } from 'enzyme'; +import { noop } from 'lodash'; +import React from 'react'; + import { SecondaryActions } from './SecondaryActions'; const addQueryRowButtonSelector = '[aria-label="Add row button"]'; diff --git a/public/app/features/explore/SecondaryActions.tsx b/public/app/features/explore/SecondaryActions.tsx index 4f56f8c8b4e..b18ccd78349 100644 --- a/public/app/features/explore/SecondaryActions.tsx +++ b/public/app/features/explore/SecondaryActions.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, HorizontalGroup, useTheme2 } from '@grafana/ui'; diff --git a/public/app/features/explore/TableContainer.test.tsx b/public/app/features/explore/TableContainer.test.tsx index 471abc061d1..5e4c8097c76 100644 --- a/public/app/features/explore/TableContainer.test.tsx +++ b/public/app/features/explore/TableContainer.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { screen, render, within } from '@testing-library/react'; -import { TableContainer } from './TableContainer'; +import React from 'react'; + import { DataFrame, toDataFrame, FieldType, InternalTimeZones } from '@grafana/data'; import { ExploreId } from 'app/types/explore'; +import { TableContainer } from './TableContainer'; + function getTable(): HTMLElement { return screen.getAllByRole('table')[0]; } diff --git a/public/app/features/explore/TableContainer.tsx b/public/app/features/explore/TableContainer.tsx index ccdf5e3a75f..a31f1bdb2f9 100644 --- a/public/app/features/explore/TableContainer.tsx +++ b/public/app/features/explore/TableContainer.tsx @@ -1,14 +1,16 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { ValueLinkConfig, applyFieldOverrides, TimeZone } from '@grafana/data'; import { Collapse, Table } from '@grafana/ui'; -import { ExploreId, ExploreItemState } from 'app/types/explore'; -import { StoreState } from 'app/types'; -import { splitOpen } from './state/main'; +import { FilterItem } from '@grafana/ui/src/components/Table/types'; import { config } from 'app/core/config'; import { PANEL_BORDER } from 'app/core/constants'; +import { StoreState } from 'app/types'; +import { ExploreId, ExploreItemState } from 'app/types/explore'; + import { MetaInfoText } from './MetaInfoText'; -import { FilterItem } from '@grafana/ui/src/components/Table/types'; +import { splitOpen } from './state/main'; import { getFieldLinksForExplore } from './utils/links'; interface TableContainerProps { diff --git a/public/app/features/explore/Time.tsx b/public/app/features/explore/Time.tsx index 01033176f9a..c5692f1a0dd 100644 --- a/public/app/features/explore/Time.tsx +++ b/public/app/features/explore/Time.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { toDuration } from '@grafana/data'; export interface TimeProps { diff --git a/public/app/features/explore/TimeSyncButton.test.tsx b/public/app/features/explore/TimeSyncButton.test.tsx index afa744155b9..653b675f195 100644 --- a/public/app/features/explore/TimeSyncButton.test.tsx +++ b/public/app/features/explore/TimeSyncButton.test.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { TimeSyncButton } from './TimeSyncButton'; import { mount } from 'enzyme'; +import React from 'react'; + +import { TimeSyncButton } from './TimeSyncButton'; const setup = (isSynced: boolean) => { const onClick = () => {}; diff --git a/public/app/features/explore/TimeSyncButton.tsx b/public/app/features/explore/TimeSyncButton.tsx index 87cfe5bc4f4..7745eccf140 100644 --- a/public/app/features/explore/TimeSyncButton.tsx +++ b/public/app/features/explore/TimeSyncButton.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Tooltip, ToolbarButton } from '@grafana/ui'; interface TimeSyncButtonProps { diff --git a/public/app/features/explore/TraceView/TraceView.test.tsx b/public/app/features/explore/TraceView/TraceView.test.tsx index 240d90bcb40..97a7cfa1655 100644 --- a/public/app/features/explore/TraceView/TraceView.test.tsx +++ b/public/app/features/explore/TraceView/TraceView.test.tsx @@ -1,14 +1,17 @@ -import React from 'react'; +import { TraceData, TraceSpanData } from '@jaegertracing/jaeger-ui-components/src/types/trace'; import { render, prettyDOM, screen } from '@testing-library/react'; -import { TraceView } from './TraceView'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { DataFrame, MutableDataFrame, getDefaultTimeRange, LoadingState } from '@grafana/data'; import { setDataSourceSrv } from '@grafana/runtime'; import { ExploreId } from 'app/types'; -import { TraceData, TraceSpanData } from '@jaegertracing/jaeger-ui-components/src/types/trace'; -import { DataFrame, MutableDataFrame, getDefaultTimeRange, LoadingState } from '@grafana/data'; + import { configureStore } from '../../../store/configureStore'; -import { Provider } from 'react-redux'; + +import { TraceView } from './TraceView'; import { transformDataFrames } from './utils/transform'; -import userEvent from '@testing-library/user-event'; function getTraceView(frames: DataFrame[]) { const store = configureStore(); @@ -92,10 +95,10 @@ describe('TraceView', () => { renderTraceViewNew(); expect(screen.queryByText(/Tags/)).toBeFalsy(); const spanView = screen.getAllByText('', { selector: 'div[data-test-id="span-view"]' })[0]; - userEvent.click(spanView); + await userEvent.click(spanView); expect(screen.queryByText(/Tags/)).toBeTruthy(); - userEvent.click(spanView); + await userEvent.click(spanView); screen.debug(screen.queryAllByText(/Tags/)); expect(screen.queryByText(/Tags/)).toBeFalsy(); }); @@ -109,36 +112,36 @@ describe('TraceView', () => { expect(ticks()).toBe('0μs274.5μs549μs823.5μs1.1ms'); }); - it('correctly shows processes for each span', () => { + it('correctly shows processes for each span', async () => { renderTraceView(); let table: HTMLElement; expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); const firstSpan = screen.getAllByText('', { selector: 'div[data-test-id="span-view"]' })[0]; - userEvent.click(firstSpan); - userEvent.click(screen.getByText(/Process/)); + await userEvent.click(firstSpan); + await userEvent.click(screen.getByText(/Process/)); table = screen.getByText('', { selector: 'div[data-test-id="KeyValueTable"]' }); expect(table.innerHTML).toContain('client-uuid-1'); - userEvent.click(firstSpan); + await userEvent.click(firstSpan); const secondSpan = screen.getAllByText('', { selector: 'div[data-test-id="span-view"]' })[1]; - userEvent.click(secondSpan); - userEvent.click(screen.getByText(/Process/)); + await userEvent.click(secondSpan); + await userEvent.click(screen.getByText(/Process/)); table = screen.getByText('', { selector: 'div[data-test-id="KeyValueTable"]' }); expect(table.innerHTML).toContain('client-uuid-2'); - userEvent.click(secondSpan); + await userEvent.click(secondSpan); const thirdSpan = screen.getAllByText('', { selector: 'div[data-test-id="span-view"]' })[2]; - userEvent.click(thirdSpan); - userEvent.click(screen.getByText(/Process/)); + await userEvent.click(thirdSpan); + await userEvent.click(screen.getByText(/Process/)); table = screen.getByText('', { selector: 'div[data-test-id="KeyValueTable"]' }); expect(table.innerHTML).toContain('client-uuid-3'); }); - it('resets detail view for new trace with the identical spanID', () => { + it('resets detail view for new trace with the identical spanID', async () => { const { rerender } = render(getTraceView([frameOld])); const span = screen.getAllByText('', { selector: 'div[data-test-id="span-view"]' })[2]; - userEvent.click(span); + await userEvent.click(span); //Process is in detail view expect(screen.getByText(/Process/)).toBeInTheDocument(); diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index cfb3b61ccfa..ff2e2bb5dee 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -1,3 +1,6 @@ +import React, { RefObject, useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + import { DataFrame, DataLink, @@ -22,9 +25,9 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getTimeZone } from 'app/features/profile/state/selectors'; import { StoreState } from 'app/types'; import { ExploreId } from 'app/types/explore'; -import React, { RefObject, useCallback, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; + import { changePanelState } from '../state/explorePane'; + import { createSpanLinkFactory } from './createSpanLink'; import { useDetailState } from './useDetailState'; import { useHoverIndentGuide } from './useHoverIndentGuide'; diff --git a/public/app/features/explore/TraceView/TraceViewContainer.test.tsx b/public/app/features/explore/TraceView/TraceViewContainer.test.tsx index 68725bb429b..89ee235f4c8 100644 --- a/public/app/features/explore/TraceView/TraceViewContainer.test.tsx +++ b/public/app/features/explore/TraceView/TraceViewContainer.test.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { TraceViewContainer } from './TraceViewContainer'; -import { frameOld } from './TraceView.test'; -import { ExploreId } from 'app/types'; -import { configureStore } from '../../../store/configureStore'; -import { getDefaultTimeRange, LoadingState } from '@grafana/data'; -import { Provider } from 'react-redux'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { getDefaultTimeRange, LoadingState } from '@grafana/data'; +import { ExploreId } from 'app/types'; + +import { configureStore } from '../../../store/configureStore'; + +import { frameOld } from './TraceView.test'; +import { TraceViewContainer } from './TraceViewContainer'; function renderTraceViewContainer(frames = [frameOld]) { const store = configureStore(); @@ -35,75 +38,75 @@ function renderTraceViewContainer(frames = [frameOld]) { } describe('TraceViewContainer', () => { - it('toggles children visibility', () => { + it('toggles children visibility', async () => { renderTraceViewContainer(); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); - userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]); + await userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(1); - userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]); + await userEvent.click(screen.getAllByText('', { selector: 'span[data-test-id="SpanTreeOffset--indentGuide"]' })[0]); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); }); - it('toggles collapses and expands one level of spans', () => { + it('toggles collapses and expands one level of spans', async () => { renderTraceViewContainer(); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); - userEvent.click(screen.getByLabelText('Collapse +1')); + await userEvent.click(screen.getByLabelText('Collapse +1')); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(2); - userEvent.click(screen.getByLabelText('Expand +1')); + await userEvent.click(screen.getByLabelText('Expand +1')); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); }); - it('toggles collapses and expands all levels', () => { + it('toggles collapses and expands all levels', async () => { renderTraceViewContainer(); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); - userEvent.click(screen.getByLabelText('Collapse All')); + await userEvent.click(screen.getByLabelText('Collapse All')); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(1); - userEvent.click(screen.getByLabelText('Expand All')); + await userEvent.click(screen.getByLabelText('Expand All')); expect(screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' }).length).toBe(3); }); - it('searches for spans', () => { + it('searches for spans', async () => { renderTraceViewContainer(); - userEvent.type(screen.getByPlaceholderText('Find...'), '1ed38015486087ca'); + await userEvent.type(screen.getByPlaceholderText('Find...'), '1ed38015486087ca'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[0].parentNode! as HTMLElement).className ).toContain('rowMatchingFilter'); }); - it('can select next/prev results', () => { + it('can select next/prev results', async () => { renderTraceViewContainer(); - userEvent.type(screen.getByPlaceholderText('Find...'), 'logproto'); + await userEvent.type(screen.getByPlaceholderText('Find...'), 'logproto'); const nextResultButton = screen.getByTestId('trace-page-search-bar-next-result-button'); const prevResultButton = screen.getByTestId('trace-page-search-bar-prev-result-button'); const suffix = screen.getByTestId('trace-page-search-bar-suffix'); - userEvent.click(nextResultButton); + await userEvent.click(nextResultButton); expect(suffix.textContent).toBe('1 of 2'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[1].parentNode! as HTMLElement).className ).toContain('rowFocused'); - userEvent.click(nextResultButton); + await userEvent.click(nextResultButton); expect(suffix.textContent).toBe('2 of 2'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[2].parentNode! as HTMLElement).className ).toContain('rowFocused'); - userEvent.click(nextResultButton); + await userEvent.click(nextResultButton); expect(suffix.textContent).toBe('1 of 2'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[1].parentNode! as HTMLElement).className ).toContain('rowFocused'); - userEvent.click(prevResultButton); + await userEvent.click(prevResultButton); expect(suffix.textContent).toBe('2 of 2'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[2].parentNode! as HTMLElement).className ).toContain('rowFocused'); - userEvent.click(prevResultButton); + await userEvent.click(prevResultButton); expect(suffix.textContent).toBe('1 of 2'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[1].parentNode! as HTMLElement).className ).toContain('rowFocused'); - userEvent.click(prevResultButton); + await userEvent.click(prevResultButton); expect(suffix.textContent).toBe('2 of 2'); expect( (screen.queryAllByText('', { selector: 'div[data-test-id="span-view"]' })[2].parentNode! as HTMLElement).className diff --git a/public/app/features/explore/TraceView/TraceViewContainer.tsx b/public/app/features/explore/TraceView/TraceViewContainer.tsx index 80abebf309c..e5bd747f68b 100644 --- a/public/app/features/explore/TraceView/TraceViewContainer.tsx +++ b/public/app/features/explore/TraceView/TraceViewContainer.tsx @@ -1,12 +1,14 @@ -import React, { RefObject, useMemo, useState } from 'react'; -import { Collapse } from '@grafana/ui'; -import { DataFrame, SplitOpen, PanelData } from '@grafana/data'; -import { TraceView } from './TraceView'; -import { ExploreId } from 'app/types/explore'; import TracePageSearchBar from '@jaegertracing/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar'; +import React, { RefObject, useMemo, useState } from 'react'; + +import { DataFrame, SplitOpen, PanelData } from '@grafana/data'; +import { Collapse } from '@grafana/ui'; +import { ExploreId } from 'app/types/explore'; + +import { TraceView } from './TraceView'; +import { useChildrenState } from './useChildrenState'; import { useSearch } from './useSearch'; import { transformDataFrames } from './utils/transform'; -import { useChildrenState } from './useChildrenState'; interface Props { dataFrames: DataFrame[]; splitOpenFn: SplitOpen; diff --git a/public/app/features/explore/TraceView/createSpanLink.test.ts b/public/app/features/explore/TraceView/createSpanLink.test.ts index 58b4a7d2145..e88ce728b73 100644 --- a/public/app/features/explore/TraceView/createSpanLink.test.ts +++ b/public/app/features/explore/TraceView/createSpanLink.test.ts @@ -1,11 +1,13 @@ import { DataSourceInstanceSettings, MutableDataFrame } from '@grafana/data'; import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime'; -import { createSpanLinkFactory } from './createSpanLink'; import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; + import { TraceToLogsOptions } from '../../../core/components/TraceToLogs/TraceToLogsSettings'; import { LinkSrv, setLinkSrv } from '../../panel/panellinks/link_srv'; import { TemplateSrv } from '../../templating/template_srv'; +import { createSpanLinkFactory } from './createSpanLink'; + describe('createSpanLinkFactory', () => { it('returns undefined if there is no data source uid', () => { const splitOpenFn = jest.fn(); diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index 5bfcd690816..a036eeb05d8 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import { DataFrame, DataLink, @@ -17,7 +19,7 @@ import { Icon } from '@grafana/ui'; import { SpanLinkDef, SpanLinkFunc, TraceSpan } from '@jaegertracing/jaeger-ui-components'; import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import React from 'react'; + import { LokiQuery } from '../../../plugins/datasource/loki/types'; import { getFieldLinksForExplore } from '../utils/links'; diff --git a/public/app/features/explore/TraceView/useChildrenState.test.ts b/public/app/features/explore/TraceView/useChildrenState.test.ts index 4871401d57a..1d6dca9c200 100644 --- a/public/app/features/explore/TraceView/useChildrenState.test.ts +++ b/public/app/features/explore/TraceView/useChildrenState.test.ts @@ -1,7 +1,9 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useChildrenState } from './useChildrenState'; + import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; +import { useChildrenState } from './useChildrenState'; + describe('useChildrenState', () => { describe('childrenToggle', () => { it('toggles children state', async () => { diff --git a/public/app/features/explore/TraceView/useChildrenState.ts b/public/app/features/explore/TraceView/useChildrenState.ts index 431c8946224..ef8607bf9ef 100644 --- a/public/app/features/explore/TraceView/useChildrenState.ts +++ b/public/app/features/explore/TraceView/useChildrenState.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react'; + import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; /** diff --git a/public/app/features/explore/TraceView/useDetailState.test.ts b/public/app/features/explore/TraceView/useDetailState.test.ts index 0a641f461c8..f52bda64446 100644 --- a/public/app/features/explore/TraceView/useDetailState.test.ts +++ b/public/app/features/explore/TraceView/useDetailState.test.ts @@ -1,8 +1,10 @@ -import { act, renderHook } from '@testing-library/react-hooks'; -import { useDetailState } from './useDetailState'; import { TraceLog } from '@jaegertracing/jaeger-ui-components/src/types/trace'; +import { act, renderHook } from '@testing-library/react-hooks'; + import { DataFrame } from '@grafana/data'; +import { useDetailState } from './useDetailState'; + const sampleFrame: DataFrame = { name: 'trace', fields: [], diff --git a/public/app/features/explore/TraceView/useDetailState.ts b/public/app/features/explore/TraceView/useDetailState.ts index c58e91f6d0d..63400a6f2f7 100644 --- a/public/app/features/explore/TraceView/useDetailState.ts +++ b/public/app/features/explore/TraceView/useDetailState.ts @@ -1,7 +1,8 @@ +import { TraceLog, TraceSpanReference } from '@jaegertracing/jaeger-ui-components/src/types/trace'; import { useCallback, useState, useEffect } from 'react'; + import { DataFrame } from '@grafana/data'; import { DetailState } from '@jaegertracing/jaeger-ui-components'; -import { TraceLog, TraceSpanReference } from '@jaegertracing/jaeger-ui-components/src/types/trace'; /** * Keeps state of the span detail. This means whether span details are open but also state of each detail subitem diff --git a/public/app/features/explore/TraceView/useHoverIndentGuide.test.ts b/public/app/features/explore/TraceView/useHoverIndentGuide.test.ts index 94a280e2182..e3a16ce351d 100644 --- a/public/app/features/explore/TraceView/useHoverIndentGuide.test.ts +++ b/public/app/features/explore/TraceView/useHoverIndentGuide.test.ts @@ -1,4 +1,5 @@ import { renderHook, act } from '@testing-library/react-hooks'; + import { useHoverIndentGuide } from './useHoverIndentGuide'; describe('useHoverIndentGuide', () => { diff --git a/public/app/features/explore/TraceView/useSearch.test.ts b/public/app/features/explore/TraceView/useSearch.test.ts index 0279229b0bd..ce5ceddbeb7 100644 --- a/public/app/features/explore/TraceView/useSearch.test.ts +++ b/public/app/features/explore/TraceView/useSearch.test.ts @@ -1,7 +1,9 @@ import { act, renderHook } from '@testing-library/react-hooks'; -import { useSearch } from './useSearch'; + import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; +import { useSearch } from './useSearch'; + describe('useSearch', () => { it('returns matching span IDs', async () => { const { result } = renderHook(() => diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index dba2602a7f3..6574ac5659b 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -1,4 +1,5 @@ import { useMemo, useState } from 'react'; + import { filterSpans, TraceSpan } from '@jaegertracing/jaeger-ui-components'; /** diff --git a/public/app/features/explore/TraceView/useViewRange.test.ts b/public/app/features/explore/TraceView/useViewRange.test.ts index 0e4a55abe93..8212c5b05cd 100644 --- a/public/app/features/explore/TraceView/useViewRange.test.ts +++ b/public/app/features/explore/TraceView/useViewRange.test.ts @@ -1,4 +1,5 @@ import { renderHook, act } from '@testing-library/react-hooks'; + import { useViewRange } from './useViewRange'; describe('useViewRange', () => { diff --git a/public/app/features/explore/TraceView/useViewRange.ts b/public/app/features/explore/TraceView/useViewRange.ts index b7706ddc238..e617f488ebe 100644 --- a/public/app/features/explore/TraceView/useViewRange.ts +++ b/public/app/features/explore/TraceView/useViewRange.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react'; + import { ViewRangeTimeUpdate, ViewRange } from '@jaegertracing/jaeger-ui-components'; /** diff --git a/public/app/features/explore/Wrapper.test.tsx b/public/app/features/explore/Wrapper.test.tsx index d26b026620b..21d2c9865ea 100644 --- a/public/app/features/explore/Wrapper.test.tsx +++ b/public/app/features/explore/Wrapper.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { fireEvent, screen, waitFor } from '@testing-library/react'; -import { locationService } from '@grafana/runtime'; -import { serializeStateToUrlParam } from '@grafana/data'; import userEvent from '@testing-library/user-event'; -import { splitOpen } from './state/main'; -import { setupExplore, tearDown, waitForExplore } from './spec/helper/setup'; -import { makeLogsQueryResponse, makeMetricsQueryResponse } from './spec/helper/query'; +import React from 'react'; + +import { serializeStateToUrlParam } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; + import { changeDatasource } from './spec/helper/interactions'; +import { makeLogsQueryResponse, makeMetricsQueryResponse } from './spec/helper/query'; +import { setupExplore, tearDown, waitForExplore } from './spec/helper/setup'; +import { splitOpen } from './state/main'; type Mock = jest.Mock; @@ -217,7 +219,7 @@ describe('Wrapper', () => { }; setupExplore({ urlParams }); const closeButtons = await screen.findAllByTitle(/Close split pane/i); - userEvent.click(closeButtons[1]); + await userEvent.click(closeButtons[1]); await waitFor(() => { const logsPanels = screen.queryAllByTitle(/Close split pane/i); diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx index 4f2d5439ea4..6afa7dd29d1 100644 --- a/public/app/features/explore/Wrapper.tsx +++ b/public/app/features/explore/Wrapper.tsx @@ -1,15 +1,18 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { ExploreId, ExploreQueryParams } from 'app/types/explore'; -import { ErrorBoundaryAlert } from '@grafana/ui'; -import { lastSavedUrl, resetExploreAction, richHistoryUpdatedAction } from './state/main'; -import { ExplorePaneContainer } from './ExplorePaneContainer'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { Branding } from '../../core/components/Branding/Branding'; -import { getNavModel } from '../../core/selectors/navModel'; -import { StoreState } from 'app/types'; import { locationService } from '@grafana/runtime'; +import { ErrorBoundaryAlert } from '@grafana/ui'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { StoreState } from 'app/types'; +import { ExploreId, ExploreQueryParams } from 'app/types/explore'; + +import { Branding } from '../../core/components/Branding/Branding'; +import { getNavModel } from '../../core/selectors/navModel'; + +import { ExploreActions } from './ExploreActions'; +import { ExplorePaneContainer } from './ExplorePaneContainer'; +import { lastSavedUrl, resetExploreAction, richHistoryUpdatedAction } from './state/main'; interface RouteProps extends GrafanaRouteComponentProps<{}, ExploreQueryParams> {} interface OwnProps {} @@ -69,6 +72,7 @@ class WrapperUnconnected extends PureComponent { return (
+
diff --git a/public/app/features/explore/exploreGraphStyleUtils.ts b/public/app/features/explore/exploreGraphStyleUtils.ts index 177fbfcead3..6da223c91b9 100644 --- a/public/app/features/explore/exploreGraphStyleUtils.ts +++ b/public/app/features/explore/exploreGraphStyleUtils.ts @@ -1,6 +1,8 @@ import produce from 'immer'; + import { FieldConfigSource } from '@grafana/data'; import { GraphDrawStyle, GraphFieldConfig, StackingMode } from '@grafana/schema'; + import { ExploreGraphStyle } from '../../types'; export type FieldConfig = FieldConfigSource; diff --git a/public/app/features/explore/spec/helper/assert.ts b/public/app/features/explore/spec/helper/assert.ts index 353455c9b9f..3bbd4c70290 100644 --- a/public/app/features/explore/spec/helper/assert.ts +++ b/public/app/features/explore/spec/helper/assert.ts @@ -1,5 +1,7 @@ import { waitFor } from '@testing-library/react'; + import { ExploreId } from '../../../../types'; + import { withinExplore } from './setup'; export const assertQueryHistoryExists = (query: string, exploreId: ExploreId = ExploreId.left) => { diff --git a/public/app/features/explore/spec/helper/interactions.ts b/public/app/features/explore/spec/helper/interactions.ts index f1ed4542cc4..84941bbd361 100644 --- a/public/app/features/explore/spec/helper/interactions.ts +++ b/public/app/features/explore/spec/helper/interactions.ts @@ -1,7 +1,10 @@ -import { selectors } from '@grafana/e2e-selectors'; -import userEvent from '@testing-library/user-event'; import { fireEvent, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { selectors } from '@grafana/e2e-selectors'; + import { ExploreId } from '../../../../types'; + import { withinExplore } from './setup'; export const changeDatasource = async (name: string) => { @@ -11,23 +14,23 @@ export const changeDatasource = async (name: string) => { fireEvent.click(option); }; -export const inputQuery = (query: string, exploreId: ExploreId = ExploreId.left) => { +export const inputQuery = async (query: string, exploreId: ExploreId = ExploreId.left) => { const input = withinExplore(exploreId).getByRole('textbox', { name: 'query' }); - userEvent.clear(input); - userEvent.type(input, query); + await userEvent.clear(input); + await userEvent.type(input, query); }; -export const runQuery = (exploreId: ExploreId = ExploreId.left) => { +export const runQuery = async (exploreId: ExploreId = ExploreId.left) => { const explore = withinExplore(exploreId); const toolbar = within(explore.getByLabelText('Explore toolbar')); const button = toolbar.getByRole('button', { name: /run query/i }); - userEvent.click(button); + await userEvent.click(button); }; export const openQueryHistory = async (exploreId: ExploreId = ExploreId.left) => { const selector = withinExplore(exploreId); const button = selector.getByRole('button', { name: 'Rich history button' }); - userEvent.click(button); + await userEvent.click(button); expect( await selector.findByText('The history is local to your browser and is not shared with others.') ).toBeInTheDocument(); @@ -35,26 +38,26 @@ export const openQueryHistory = async (exploreId: ExploreId = ExploreId.left) => export const closeQueryHistory = async (exploreId: ExploreId = ExploreId.left) => { const closeButton = withinExplore(exploreId).getByRole('button', { name: 'Close query history' }); - userEvent.click(closeButton); + await userEvent.click(closeButton); }; export const switchToQueryHistoryTab = async ( name: 'Settings' | 'Query History', exploreId: ExploreId = ExploreId.left ) => { - userEvent.click(withinExplore(exploreId).getByRole('tab', { name: `Tab ${name}` })); + await userEvent.click(withinExplore(exploreId).getByRole('tab', { name: `Tab ${name}` })); }; -export const selectStarredTabFirst = (exploreId: ExploreId = ExploreId.left) => { +export const selectStarredTabFirst = async (exploreId: ExploreId = ExploreId.left) => { const checkbox = withinExplore(exploreId).getByRole('checkbox', { name: 'Change the default active tab from “Query history” to “Starred”', }); - userEvent.click(checkbox); + await userEvent.click(checkbox); }; -export const selectOnlyActiveDataSource = (exploreId: ExploreId = ExploreId.left) => { +export const selectOnlyActiveDataSource = async (exploreId: ExploreId = ExploreId.left) => { const checkbox = withinExplore(exploreId).getByLabelText(/Only show queries for data source currently active.*/); - userEvent.click(checkbox); + await userEvent.click(checkbox); }; export const starQueryHistory = (queryIndex: number, exploreId: ExploreId = ExploreId.left) => { @@ -65,8 +68,8 @@ export const deleteQueryHistory = (queryIndex: number, exploreId: ExploreId = Ex invokeAction(queryIndex, 'Delete query', exploreId); }; -const invokeAction = (queryIndex: number, actionAccessibleName: string, exploreId: ExploreId) => { +const invokeAction = async (queryIndex: number, actionAccessibleName: string, exploreId: ExploreId) => { const selector = withinExplore(exploreId); const buttons = selector.getAllByRole('button', { name: actionAccessibleName }); - userEvent.click(buttons[queryIndex]); + await userEvent.click(buttons[queryIndex]); }; diff --git a/public/app/features/explore/spec/helper/query.ts b/public/app/features/explore/spec/helper/query.ts index b1156fc6673..a1ec1216b64 100644 --- a/public/app/features/explore/spec/helper/query.ts +++ b/public/app/features/explore/spec/helper/query.ts @@ -1,4 +1,5 @@ import { from, Observable } from 'rxjs'; + import { ArrayDataFrame, DataQueryResponse, FieldType } from '@grafana/data'; export function makeLogsQueryResponse(marker = ''): Observable { diff --git a/public/app/features/explore/spec/helper/setup.tsx b/public/app/features/explore/spec/helper/setup.tsx index ef2930b6717..ad56d246106 100644 --- a/public/app/features/explore/spec/helper/setup.tsx +++ b/public/app/features/explore/spec/helper/setup.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; import { within } from '@testing-library/dom'; +import { render, screen } from '@testing-library/react'; +import { fromPairs } from 'lodash'; +import React from 'react'; import { Provider } from 'react-redux'; import { Route, Router } from 'react-router-dom'; -import { fromPairs } from 'lodash'; import { DataSourceApi, DataSourceInstanceSettings, QueryEditorProps, ScopedVars } from '@grafana/data'; import { locationService, setDataSourceSrv, setEchoSrv } from '@grafana/runtime'; @@ -11,12 +11,11 @@ import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute'; import { Echo } from 'app/core/services/echo/Echo'; import { configureStore } from 'app/store/configureStore'; -import Wrapper from '../../Wrapper'; -import { initialUserState } from '../../../profile/state/reducers'; - import { LokiDatasource } from '../../../../plugins/datasource/loki/datasource'; import { LokiQuery } from '../../../../plugins/datasource/loki/types'; import { ExploreId } from '../../../../types'; +import { initialUserState } from '../../../profile/state/reducers'; +import Wrapper from '../../Wrapper'; type DatasourceSetup = { settings: DataSourceInstanceSettings; api: DataSourceApi }; diff --git a/public/app/features/explore/spec/interpolation.test.tsx b/public/app/features/explore/spec/interpolation.test.tsx index cfe11ccbc0f..8e13045701f 100644 --- a/public/app/features/explore/spec/interpolation.test.tsx +++ b/public/app/features/explore/spec/interpolation.test.tsx @@ -1,10 +1,13 @@ import React from 'react'; -import { setupExplore, waitForExplore } from './helper/setup'; -import { makeLogsQueryResponse } from './helper/query'; + import { DataQueryRequest, serializeStateToUrlParam } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; + import { LokiQuery } from '../../../plugins/datasource/loki/types'; +import { makeLogsQueryResponse } from './helper/query'; +import { setupExplore, waitForExplore } from './helper/setup'; + jest.mock('react-virtualized-auto-sizer', () => { return { __esModule: true, diff --git a/public/app/features/explore/spec/queryHistory.test.tsx b/public/app/features/explore/spec/queryHistory.test.tsx index b62c8d2558d..af35b403341 100644 --- a/public/app/features/explore/spec/queryHistory.test.tsx +++ b/public/app/features/explore/spec/queryHistory.test.tsx @@ -1,6 +1,17 @@ import React from 'react'; + import { serializeStateToUrlParam } from '@grafana/data'; -import { setupExplore, tearDown, waitForExplore } from './helper/setup'; + +import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import { ExploreId } from '../../../types'; + +import { + assertDataSourceFilterVisibility, + assertQueryHistory, + assertQueryHistoryExists, + assertQueryHistoryIsStarred, + assertQueryHistoryTabIsSelected, +} from './helper/assert'; import { closeQueryHistory, deleteQueryHistory, @@ -12,16 +23,8 @@ import { starQueryHistory, switchToQueryHistoryTab, } from './helper/interactions'; -import { - assertDataSourceFilterVisibility, - assertQueryHistory, - assertQueryHistoryExists, - assertQueryHistoryIsStarred, - assertQueryHistoryTabIsSelected, -} from './helper/assert'; import { makeLogsQueryResponse } from './helper/query'; -import { ExploreId } from '../../../types'; -import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import { setupExplore, tearDown, waitForExplore } from './helper/setup'; jest.mock('react-virtualized-auto-sizer', () => { return { @@ -49,8 +52,8 @@ describe('Explore: Query History', () => { await waitForExplore(); // and a user runs a query and opens query history - inputQuery(USER_INPUT); - runQuery(); + await inputQuery(USER_INPUT); + await runQuery(); await openQueryHistory(); // the query that was run is in query history @@ -80,8 +83,8 @@ describe('Explore: Query History', () => { await waitForExplore(); await openQueryHistory(); - inputQuery('query #2'); - runQuery(); + await inputQuery('query #2'); + await runQuery(); await assertQueryHistory(['{"expr":"query #2"}', '{"expr":"query #1"}']); }); @@ -135,8 +138,8 @@ describe('Explore: Query History', () => { await switchToQueryHistoryTab('Settings'); // change settings - selectStarredTabFirst(); - selectOnlyActiveDataSource(); + await selectStarredTabFirst(); + await selectOnlyActiveDataSource(); await closeQueryHistory(); await openQueryHistory(); diff --git a/public/app/features/explore/state/datasource.test.ts b/public/app/features/explore/state/datasource.test.ts index 35fe417622c..01a28e8dd95 100644 --- a/public/app/features/explore/state/datasource.test.ts +++ b/public/app/features/explore/state/datasource.test.ts @@ -1,6 +1,7 @@ -import { updateDatasourceInstanceAction, datasourceReducer } from './datasource'; -import { ExploreId, ExploreItemState } from 'app/types'; import { DataQuery, DataSourceApi } from '@grafana/data'; +import { ExploreId, ExploreItemState } from 'app/types'; + +import { updateDatasourceInstanceAction, datasourceReducer } from './datasource'; import { createEmptyQueryResponse } from './utils'; describe('Datasource reducer', () => { diff --git a/public/app/features/explore/state/datasource.ts b/public/app/features/explore/state/datasource.ts index abaab9ad04d..a4564d0fe5b 100644 --- a/public/app/features/explore/state/datasource.ts +++ b/public/app/features/explore/state/datasource.ts @@ -1,11 +1,12 @@ // Libraries import { AnyAction, createAction } from '@reduxjs/toolkit'; -import { RefreshPicker } from '@grafana/ui'; + import { DataSourceApi, HistoryItem } from '@grafana/data'; +import { RefreshPicker } from '@grafana/ui'; import { stopQueryState } from 'app/core/utils/explore'; import { ExploreItemState, ThunkResult } from 'app/types'; - import { ExploreId } from 'app/types/explore'; + import { importQueries, runQueries } from './query'; import { changeRefreshInterval } from './time'; import { createEmptyQueryResponse, loadAndInitDatasource } from './utils'; diff --git a/public/app/features/explore/state/explorePane.test.ts b/public/app/features/explore/state/explorePane.test.ts index e429616a7d6..70b957d17d6 100644 --- a/public/app/features/explore/state/explorePane.test.ts +++ b/public/app/features/explore/state/explorePane.test.ts @@ -1,9 +1,12 @@ -import { serializeStateToUrlParam } from '@grafana/data'; -import { ExploreId, StoreState, ThunkDispatch } from 'app/types'; -import { refreshExplore } from './explorePane'; -import { setDataSourceSrv } from '@grafana/runtime'; -import { configureStore } from '../../../store/configureStore'; import { of } from 'rxjs'; + +import { serializeStateToUrlParam } from '@grafana/data'; +import { setDataSourceSrv } from '@grafana/runtime'; +import { ExploreId, StoreState, ThunkDispatch } from 'app/types'; + +import { configureStore } from '../../../store/configureStore'; + +import { refreshExplore } from './explorePane'; import { createDefaultInitialState } from './helpers'; jest.mock('../../dashboard/services/TimeSrv', () => ({ diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index a9265a441fa..98dac17a6a2 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -1,27 +1,7 @@ -import { AnyAction } from 'redux'; -import { isEqual } from 'lodash'; - -import { - DEFAULT_RANGE, - getQueryKeys, - parseUrlState, - ensureQueries, - generateNewKeyAndAddRefIdIfMissing, - getTimeRangeFromUrl, -} from 'app/core/utils/explore'; -import { ExploreGraphStyle, ExploreId, ExploreItemState } from 'app/types/explore'; -import { queryReducer, runQueries, setQueriesAction } from './query'; -import { datasourceReducer } from './datasource'; -import { timeReducer, updateTime } from './time'; -import { historyReducer } from './history'; -import { - makeExplorePaneState, - loadAndInitDatasource, - createEmptyQueryResponse, - getUrlStateFromPaneState, - storeGraphStyle, -} from './utils'; import { createAction, PayloadAction } from '@reduxjs/toolkit'; +import { isEqual } from 'lodash'; +import { AnyAction } from 'redux'; + import { EventBusExtended, DataQuery, @@ -32,12 +12,33 @@ import { ExplorePanelsState, PreferredVisualisationType, } from '@grafana/data'; -// Types -import { ThunkResult } from 'app/types'; -import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; import { getDataSourceSrv } from '@grafana/runtime'; -import { richHistorySearchFiltersUpdatedAction, richHistoryUpdatedAction, stateSave } from './main'; import { keybindingSrv } from 'app/core/services/keybindingSrv'; +import { + DEFAULT_RANGE, + getQueryKeys, + parseUrlState, + ensureQueries, + generateNewKeyAndAddRefIdIfMissing, + getTimeRangeFromUrl, +} from 'app/core/utils/explore'; +import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; +import { ThunkResult } from 'app/types'; +import { ExploreGraphStyle, ExploreId, ExploreItemState } from 'app/types/explore'; + +import { datasourceReducer } from './datasource'; +import { historyReducer } from './history'; +import { richHistorySearchFiltersUpdatedAction, richHistoryUpdatedAction, stateSave } from './main'; +import { queryReducer, runQueries, setQueriesAction } from './query'; +import { timeReducer, updateTime } from './time'; +import { + makeExplorePaneState, + loadAndInitDatasource, + createEmptyQueryResponse, + getUrlStateFromPaneState, + storeGraphStyle, +} from './utils'; +// Types // // Actions and Payloads diff --git a/public/app/features/explore/state/helpers.ts b/public/app/features/explore/state/helpers.ts index 5e744449262..2a9e39f46a6 100644 --- a/public/app/features/explore/state/helpers.ts +++ b/public/app/features/explore/state/helpers.ts @@ -1,4 +1,5 @@ import { DefaultTimeZone, toUtc } from '@grafana/data'; + import { ExploreId } from '../../../types'; export const createDefaultInitialState = () => { diff --git a/public/app/features/explore/state/history.ts b/public/app/features/explore/state/history.ts index 4c5982514db..cd4e6375310 100644 --- a/public/app/features/explore/state/history.ts +++ b/public/app/features/explore/state/history.ts @@ -1,3 +1,6 @@ +import { AnyAction, createAction } from '@reduxjs/toolkit'; + +import { DataQuery, HistoryItem } from '@grafana/data'; import { addToRichHistory, deleteAllFromRichHistory, @@ -10,6 +13,9 @@ import { updateStarredInRichHistory, } from 'app/core/utils/richHistory'; import { ExploreId, ExploreItemState, ExploreState, RichHistoryQuery, ThunkResult } from 'app/types'; + +import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/utils/richHistoryTypes'; + import { richHistoryLimitExceededAction, richHistorySearchFiltersUpdatedAction, @@ -17,9 +23,6 @@ import { richHistoryStorageFullAction, richHistoryUpdatedAction, } from './main'; -import { DataQuery, HistoryItem } from '@grafana/data'; -import { AnyAction, createAction } from '@reduxjs/toolkit'; -import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/utils/richHistoryTypes'; // // Actions and Payloads diff --git a/public/app/features/explore/state/main.test.ts b/public/app/features/explore/state/main.test.ts index a964eafb4b4..3a5d5839784 100644 --- a/public/app/features/explore/state/main.test.ts +++ b/public/app/features/explore/state/main.test.ts @@ -1,12 +1,15 @@ -import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; -import { exploreReducer, navigateToExplore, splitCloseAction } from './main'; import { thunkTester } from 'test/core/thunk/thunkTester'; + +import { ExploreUrlState } from '@grafana/data'; +import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; +import { locationService } from '@grafana/runtime'; import { PanelModel } from 'app/features/dashboard/state'; + +import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { MockDataSourceApi } from '../../../../test/mocks/datasource_srv'; import { ExploreId, ExploreItemState, ExploreState } from '../../../types'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { ExploreUrlState } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; + +import { exploreReducer, navigateToExplore, splitCloseAction } from './main'; const getNavigateToExploreContext = async (openInNewWindow?: (url: string) => void) => { const url = '/explore'; diff --git a/public/app/features/explore/state/main.ts b/public/app/features/explore/state/main.ts index cd439b72d85..32de466c90f 100644 --- a/public/app/features/explore/state/main.ts +++ b/public/app/features/explore/state/main.ts @@ -1,15 +1,18 @@ -import { AnyAction } from 'redux'; -import { DataSourceSrv, getDataSourceSrv, locationService } from '@grafana/runtime'; -import { ExploreUrlState, serializeStateToUrlParam, SplitOpen, UrlQueryMap } from '@grafana/data'; -import { GetExploreUrlArguments, stopQueryState } from 'app/core/utils/explore'; -import { ExploreId, ExploreItemState, ExploreState, RichHistoryQuery } from 'app/types/explore'; -import { paneReducer } from './explorePane'; import { createAction } from '@reduxjs/toolkit'; -import { getUrlStateFromPaneState, makeExplorePaneState } from './utils'; +import { AnyAction } from 'redux'; + +import { ExploreUrlState, serializeStateToUrlParam, SplitOpen, UrlQueryMap } from '@grafana/data'; +import { DataSourceSrv, getDataSourceSrv, locationService } from '@grafana/runtime'; +import { GetExploreUrlArguments, stopQueryState } from 'app/core/utils/explore'; +import { PanelModel } from 'app/features/dashboard/state'; +import { ExploreId, ExploreItemState, ExploreState, RichHistoryQuery } from 'app/types/explore'; + +import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/utils/richHistoryTypes'; import { ThunkResult } from '../../../types'; import { TimeSrv } from '../../dashboard/services/TimeSrv'; -import { PanelModel } from 'app/features/dashboard/state'; -import { RichHistorySearchFilters, RichHistorySettings } from '../../../core/utils/richHistoryTypes'; + +import { paneReducer } from './explorePane'; +import { getUrlStateFromPaneState, makeExplorePaneState } from './utils'; // // Actions and Payloads diff --git a/public/app/features/explore/state/query.test.ts b/public/app/features/explore/state/query.test.ts index c0d235cfd0d..7ca0899a377 100644 --- a/public/app/features/explore/state/query.test.ts +++ b/public/app/features/explore/state/query.test.ts @@ -1,3 +1,26 @@ +import { EMPTY, interval, Observable, of } from 'rxjs'; +import { thunkTester } from 'test/core/thunk/thunkTester'; + +import { + ArrayVector, + DataFrame, + DataQuery, + DataQueryResponse, + DataSourceApi, + DataSourceJsonData, + DataSourceWithLogsVolumeSupport, + LoadingState, + MutableDataFrame, + PanelData, + RawTimeRange, +} from '@grafana/data'; +import { ExploreId, ExploreItemState, StoreState, ThunkDispatch } from 'app/types'; + +import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { configureStore } from '../../../store/configureStore'; +import { setTimeSrv } from '../../dashboard/services/TimeSrv'; + +import { createDefaultInitialState } from './helpers'; import { addQueryRowAction, addResultsToCache, @@ -12,28 +35,9 @@ import { scanStopAction, storeLogsVolumeDataProviderAction, } from './query'; -import { ExploreId, ExploreItemState, StoreState, ThunkDispatch } from 'app/types'; -import { EMPTY, interval, Observable, of } from 'rxjs'; -import { - ArrayVector, - DataFrame, - DataQuery, - DataQueryResponse, - DataSourceApi, - DataSourceJsonData, - DataSourceWithLogsVolumeSupport, - LoadingState, - MutableDataFrame, - PanelData, - RawTimeRange, -} from '@grafana/data'; -import { thunkTester } from 'test/core/thunk/thunkTester'; import { makeExplorePaneState } from './utils'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { configureStore } from '../../../store/configureStore'; -import { setTimeSrv } from '../../dashboard/services/TimeSrv'; + import Mock = jest.Mock; -import { createDefaultInitialState } from './helpers'; const { testRange, defaultInitialState } = createDefaultInitialState(); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 10216ecdb3c..db4101ca86d 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -1,5 +1,8 @@ -import { mergeMap, throttleTime } from 'rxjs/operators'; +import { AnyAction, createAction, PayloadAction } from '@reduxjs/toolkit'; +import deepEqual from 'fast-deep-equal'; import { identity, Observable, of, SubscriptionLike, Unsubscribable } from 'rxjs'; +import { mergeMap, throttleTime } from 'rxjs/operators'; + import { AbsoluteTimeRange, DataQuery, @@ -16,7 +19,6 @@ import { QueryFixAction, toLegacyResponseData, } from '@grafana/data'; - import { buildQueryTransaction, ensureQueries, @@ -27,20 +29,20 @@ import { stopQueryState, updateHistory, } from 'app/core/utils/explore'; +import { getShiftedTimeRange } from 'app/core/utils/timePicker'; +import { getTimeZone } from 'app/features/profile/state/selectors'; import { ExploreItemState, ExplorePanelData, ThunkDispatch, ThunkResult } from 'app/types'; import { ExploreId, ExploreState, QueryOptions } from 'app/types/explore'; -import { getTimeZone } from 'app/features/profile/state/selectors'; -import { getShiftedTimeRange } from 'app/core/utils/timePicker'; + import { notifyApp } from '../../../core/actions'; +import { createErrorNotification } from '../../../core/copy/appNotification'; import { runRequest } from '../../query/state/runRequest'; import { decorateData } from '../utils/decorators'; -import { createErrorNotification } from '../../../core/copy/appNotification'; -import { stateSave } from './main'; -import { AnyAction, createAction, PayloadAction } from '@reduxjs/toolkit'; -import { updateTime } from './time'; + import { addHistoryItem, historyUpdatedAction, loadRichHistory } from './history'; +import { stateSave } from './main'; +import { updateTime } from './time'; import { createCacheKey, getResultsFromCache } from './utils'; -import deepEqual from 'fast-deep-equal'; // // Actions and Payloads diff --git a/public/app/features/explore/state/time.test.ts b/public/app/features/explore/state/time.test.ts index 513887c8e47..ae9e4b0a889 100644 --- a/public/app/features/explore/state/time.test.ts +++ b/public/app/features/explore/state/time.test.ts @@ -1,13 +1,15 @@ -import { dateTime, LoadingState } from '@grafana/data'; - -import { makeExplorePaneState } from './utils'; -import { ExploreId, ExploreItemState } from 'app/types/explore'; import { reducerTester } from 'test/core/redux/reducerTester'; -import { changeRangeAction, changeRefreshIntervalAction, timeReducer, updateTime } from './time'; -import { createDefaultInitialState } from './helpers'; + +import { dateTime, LoadingState } from '@grafana/data'; import { configureStore } from 'app/store/configureStore'; +import { ExploreId, ExploreItemState } from 'app/types/explore'; + import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import { createDefaultInitialState } from './helpers'; +import { changeRangeAction, changeRefreshIntervalAction, timeReducer, updateTime } from './time'; +import { makeExplorePaneState } from './utils'; + const MOCK_TIME_RANGE = {}; const mockTimeSrv = { diff --git a/public/app/features/explore/state/time.ts b/public/app/features/explore/state/time.ts index 32ccf4c3255..7ea79783a45 100644 --- a/public/app/features/explore/state/time.ts +++ b/public/app/features/explore/state/time.ts @@ -1,4 +1,5 @@ import { AnyAction, createAction, PayloadAction } from '@reduxjs/toolkit'; + import { AbsoluteTimeRange, dateTimeForTimeZone, @@ -7,17 +8,18 @@ import { sortLogsResult, TimeRange, } from '@grafana/data'; -import { RefreshPicker } from '@grafana/ui'; import { getTemplateSrv } from '@grafana/runtime'; - +import { RefreshPicker } from '@grafana/ui'; import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore'; +import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; import { ExploreItemState, ThunkResult } from 'app/types'; import { ExploreId } from 'app/types/explore'; -import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors'; + import { getTimeSrv } from '../../dashboard/services/TimeSrv'; import { TimeModel } from '../../dashboard/state/TimeModel'; -import { runQueries } from './query'; + import { syncTimesAction, stateSave } from './main'; +import { runQueries } from './query'; // // Actions and Payloads diff --git a/public/app/features/explore/state/utils.test.ts b/public/app/features/explore/state/utils.test.ts index e24ba97000d..c2834f792b3 100644 --- a/public/app/features/explore/state/utils.test.ts +++ b/public/app/features/explore/state/utils.test.ts @@ -1,5 +1,5 @@ -import { lastUsedDatasourceKeyForOrgId } from '../../../core/utils/explore'; import store from '../../../core/store'; +import { lastUsedDatasourceKeyForOrgId } from '../../../core/utils/explore'; const dataSourceMock = { get: jest.fn(), diff --git a/public/app/features/explore/state/utils.ts b/public/app/features/explore/state/utils.ts index 563c86326eb..13ba2788f50 100644 --- a/public/app/features/explore/state/utils.ts +++ b/public/app/features/explore/state/utils.ts @@ -1,3 +1,5 @@ +import { isEmpty, isObject, mapValues, omitBy } from 'lodash'; + import { AbsoluteTimeRange, DataSourceApi, @@ -10,11 +12,11 @@ import { } from '@grafana/data'; import { ExplorePanelData } from 'app/types'; import { ExploreGraphStyle, ExploreItemState } from 'app/types/explore'; -import { getDatasourceSrv } from '../../plugins/datasource_srv'; + import store from '../../../core/store'; import { clearQueryKeys, lastUsedDatasourceKeyForOrgId, toGraphStyle } from '../../../core/utils/explore'; +import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { toRawTimeRange } from '../utils/time'; -import { isEmpty, isObject, mapValues, omitBy } from 'lodash'; export const DEFAULT_RANGE = { from: 'now-6h', diff --git a/public/app/features/explore/useLiveTailControls.ts b/public/app/features/explore/useLiveTailControls.ts index b18bd8b8409..84004381429 100644 --- a/public/app/features/explore/useLiveTailControls.ts +++ b/public/app/features/explore/useLiveTailControls.ts @@ -1,11 +1,13 @@ import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; + import { RefreshPicker } from '@grafana/ui'; -import { changeRefreshInterval } from './state/time'; -import { setPausedStateAction, runQueries } from './state/query'; import { ExploreId } from '../../types'; +import { setPausedStateAction, runQueries } from './state/query'; +import { changeRefreshInterval } from './state/time'; + /** * Hook that gives you all the functions needed to control the live tailing. */ diff --git a/public/app/features/explore/utils/LogsCrossFadeTransition.tsx b/public/app/features/explore/utils/LogsCrossFadeTransition.tsx index 259a5930c7a..8ccc6eba5b5 100644 --- a/public/app/features/explore/utils/LogsCrossFadeTransition.tsx +++ b/public/app/features/explore/utils/LogsCrossFadeTransition.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import memoizeOne from 'memoize-one'; import { css } from '@emotion/css'; +import memoizeOne from 'memoize-one'; +import React from 'react'; import { CSSTransition } from 'react-transition-group'; const transitionDuration = 500; diff --git a/public/app/features/explore/utils/decorators.test.ts b/public/app/features/explore/utils/decorators.test.ts index a2ef493e7ab..893a0894e10 100644 --- a/public/app/features/explore/utils/decorators.test.ts +++ b/public/app/features/explore/utils/decorators.test.ts @@ -1,5 +1,5 @@ -import { GraphDrawStyle, StackingMode } from '@grafana/schema'; import { lastValueFrom } from 'rxjs'; + import { ArrayVector, DataFrame, @@ -11,6 +11,9 @@ import { TimeRange, toDataFrame, } from '@grafana/data'; +import { GraphDrawStyle, StackingMode } from '@grafana/schema'; +import TableModel from 'app/core/table_model'; +import { ExplorePanelData } from 'app/types'; import { decorateWithFrameTypeMetadata, @@ -18,8 +21,6 @@ import { decorateWithLogsResult, decorateWithTableResult, } from './decorators'; -import { ExplorePanelData } from 'app/types'; -import TableModel from 'app/core/table_model'; jest.mock('@grafana/data', () => ({ ...(jest.requireActual('@grafana/data') as any), diff --git a/public/app/features/explore/utils/decorators.ts b/public/app/features/explore/utils/decorators.ts index 249421956db..b2323eeffab 100644 --- a/public/app/features/explore/utils/decorators.ts +++ b/public/app/features/explore/utils/decorators.ts @@ -1,3 +1,7 @@ +import { groupBy } from 'lodash'; +import { Observable, of } from 'rxjs'; +import { map, mergeMap } from 'rxjs/operators'; + import { AbsoluteTimeRange, DataFrame, @@ -9,9 +13,7 @@ import { DataQuery, } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { groupBy } from 'lodash'; -import { Observable, of } from 'rxjs'; -import { map, mergeMap } from 'rxjs/operators'; + import { dataFrameToLogsModel } from '../../../core/logs_model'; import { refreshIntervalToSortOrder } from '../../../core/utils/explore'; import { ExplorePanelData } from '../../../types'; diff --git a/public/app/features/explore/utils/links.test.ts b/public/app/features/explore/utils/links.test.ts index c1e071b5c40..29e35f9a32e 100644 --- a/public/app/features/explore/utils/links.test.ts +++ b/public/app/features/explore/utils/links.test.ts @@ -1,4 +1,3 @@ -import { getFieldLinksForExplore } from './links'; import { ArrayVector, DataLink, @@ -10,8 +9,11 @@ import { TimeRange, } from '@grafana/data'; import { setTemplateSrv } from '@grafana/runtime'; -import { setLinkSrv } from '../../panel/panellinks/link_srv'; + import { setContextSrv } from '../../../core/services/context_srv'; +import { setLinkSrv } from '../../panel/panellinks/link_srv'; + +import { getFieldLinksForExplore } from './links'; describe('getFieldLinksForExplore', () => { beforeEach(() => { diff --git a/public/app/features/explore/utils/links.ts b/public/app/features/explore/utils/links.ts index c3aabab2a51..92198743149 100644 --- a/public/app/features/explore/utils/links.ts +++ b/public/app/features/explore/utils/links.ts @@ -1,4 +1,5 @@ import { useCallback } from 'react'; + import { Field, LinkModel, @@ -11,9 +12,10 @@ import { SplitOpen, } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; -import { getLinkSrv } from '../../panel/panellinks/link_srv'; import { contextSrv } from 'app/core/services/context_srv'; +import { getLinkSrv } from '../../panel/panellinks/link_srv'; + /** * Get links from the field of a dataframe and in addition check if there is associated * metadata with datasource in which case we will add onClick to open the link in new split window. This assumes diff --git a/public/app/features/expressions/ExpressionDatasource.test.ts b/public/app/features/expressions/ExpressionDatasource.test.ts index 139ec38c23c..c90a2107ca6 100644 --- a/public/app/features/expressions/ExpressionDatasource.test.ts +++ b/public/app/features/expressions/ExpressionDatasource.test.ts @@ -1,5 +1,6 @@ import { DataSourceInstanceSettings } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; + import { ExpressionDatasourceApi } from './ExpressionDatasource'; import { ExpressionQueryType } from './types'; diff --git a/public/app/features/expressions/ExpressionDatasource.ts b/public/app/features/expressions/ExpressionDatasource.ts index 15983e97bf5..cb9d4f16be6 100644 --- a/public/app/features/expressions/ExpressionDatasource.ts +++ b/public/app/features/expressions/ExpressionDatasource.ts @@ -1,3 +1,5 @@ +import { Observable, from, mergeMap } from 'rxjs'; + import { DataQueryRequest, DataQueryResponse, @@ -6,11 +8,11 @@ import { PluginType, ScopedVars, } from '@grafana/data'; -import { ExpressionQuery, ExpressionQueryType } from './types'; -import { ExpressionQueryEditor } from './ExpressionQueryEditor'; import { DataSourceWithBackend, getDataSourceSrv, getTemplateSrv } from '@grafana/runtime'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; -import { Observable, from, mergeMap } from 'rxjs'; + +import { ExpressionQueryEditor } from './ExpressionQueryEditor'; +import { ExpressionQuery, ExpressionQueryType } from './types'; /** * This is a singleton instance that just pretends to be a DataSource diff --git a/public/app/features/expressions/ExpressionQueryEditor.tsx b/public/app/features/expressions/ExpressionQueryEditor.tsx index 85783f48959..6e5d8aa5eee 100644 --- a/public/app/features/expressions/ExpressionQueryEditor.tsx +++ b/public/app/features/expressions/ExpressionQueryEditor.tsx @@ -1,12 +1,14 @@ import React, { PureComponent } from 'react'; + import { DataSourceApi, QueryEditorProps, SelectableValue } from '@grafana/data'; import { InlineField, Select } from '@grafana/ui'; -import { Resample } from './components/Resample'; -import { Reduce } from './components/Reduce'; -import { Math } from './components/Math'; + import { ClassicConditions } from './components/ClassicConditions'; -import { getDefaults } from './utils/expressionTypes'; +import { Math } from './components/Math'; +import { Reduce } from './components/Reduce'; +import { Resample } from './components/Resample'; import { ExpressionQuery, ExpressionQueryType, gelTypes } from './types'; +import { getDefaults } from './utils/expressionTypes'; type Props = QueryEditorProps, ExpressionQuery>; diff --git a/public/app/features/expressions/components/ClassicConditions.tsx b/public/app/features/expressions/components/ClassicConditions.tsx index 1903bdd3c8b..6496702d88f 100644 --- a/public/app/features/expressions/components/ClassicConditions.tsx +++ b/public/app/features/expressions/components/ClassicConditions.tsx @@ -1,10 +1,13 @@ import React, { FC } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Button, Icon, InlineField, InlineFieldRow } from '@grafana/ui'; -import { Condition } from './Condition'; + import { ClassicCondition, ExpressionQuery } from '../types'; import { defaultCondition } from '../utils/expressionTypes'; +import { Condition } from './Condition'; + interface Props { query: ExpressionQuery; refIds: Array>; diff --git a/public/app/features/expressions/components/Condition.tsx b/public/app/features/expressions/components/Condition.tsx index a9df2ac796e..973d099ab09 100644 --- a/public/app/features/expressions/components/Condition.tsx +++ b/public/app/features/expressions/components/Condition.tsx @@ -1,7 +1,9 @@ -import React, { FC, FormEvent } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, FormEvent } from 'react'; + import { GrafanaTheme, SelectableValue } from '@grafana/data'; import { Button, ButtonSelect, Icon, InlineFieldRow, Input, Select, useStyles } from '@grafana/ui'; + import alertDef, { EvalFunction } from '../../alerting/state/alertDef'; import { ClassicCondition, ReducerType } from '../types'; diff --git a/public/app/features/expressions/components/Math.tsx b/public/app/features/expressions/components/Math.tsx index 2d227321efd..140e569b20b 100644 --- a/public/app/features/expressions/components/Math.tsx +++ b/public/app/features/expressions/components/Math.tsx @@ -1,6 +1,8 @@ -import { InlineField, TextArea } from '@grafana/ui'; import { css } from '@emotion/css'; import React, { ChangeEvent, FC } from 'react'; + +import { InlineField, TextArea } from '@grafana/ui'; + import { ExpressionQuery } from '../types'; interface Props { diff --git a/public/app/features/expressions/components/Reduce.tsx b/public/app/features/expressions/components/Reduce.tsx index 693a66a5371..6e95add1e69 100644 --- a/public/app/features/expressions/components/Reduce.tsx +++ b/public/app/features/expressions/components/Reduce.tsx @@ -1,6 +1,8 @@ import React, { FC } from 'react'; + import { SelectableValue } from '@grafana/data'; import { InlineField, InlineFieldRow, Input, Select } from '@grafana/ui'; + import { ExpressionQuery, ExpressionQuerySettings, ReducerMode, reducerMode, reducerTypes } from '../types'; interface Props { diff --git a/public/app/features/expressions/components/Resample.tsx b/public/app/features/expressions/components/Resample.tsx index 8cc3d606531..ca37957d066 100644 --- a/public/app/features/expressions/components/Resample.tsx +++ b/public/app/features/expressions/components/Resample.tsx @@ -1,6 +1,8 @@ import React, { ChangeEvent, FC } from 'react'; + import { SelectableValue } from '@grafana/data'; import { InlineField, InlineFieldRow, Input, Select } from '@grafana/ui'; + import { downsamplingTypes, ExpressionQuery, upsamplingTypes } from '../types'; interface Props { diff --git a/public/app/features/expressions/guards.ts b/public/app/features/expressions/guards.ts index 67caaa7e7c7..e22053d5b72 100644 --- a/public/app/features/expressions/guards.ts +++ b/public/app/features/expressions/guards.ts @@ -1,5 +1,6 @@ import { DataQuery } from '@grafana/data'; import { isExpressionReference } from '@grafana/runtime/src/utils/DataSourceWithBackend'; + import { ExpressionQuery, ExpressionQueryType } from './types'; export const isExpressionQuery = (dataQuery?: DataQuery): dataQuery is ExpressionQuery => { diff --git a/public/app/features/expressions/types.ts b/public/app/features/expressions/types.ts index aaaa71faec9..d767347efcf 100644 --- a/public/app/features/expressions/types.ts +++ b/public/app/features/expressions/types.ts @@ -1,4 +1,5 @@ import { DataQuery, ReducerID, SelectableValue } from '@grafana/data'; + import { EvalFunction } from '../alerting/state/alertDef'; export enum ExpressionQueryType { diff --git a/public/app/features/expressions/utils/expressionTypes.ts b/public/app/features/expressions/utils/expressionTypes.ts index 6399334a2f1..d3e3665963b 100644 --- a/public/app/features/expressions/utils/expressionTypes.ts +++ b/public/app/features/expressions/utils/expressionTypes.ts @@ -1,6 +1,7 @@ import { ReducerID } from '@grafana/data'; -import { ClassicCondition, ExpressionQuery, ExpressionQueryType } from '../types'; + import { EvalFunction } from '../../alerting/state/alertDef'; +import { ClassicCondition, ExpressionQuery, ExpressionQueryType } from '../types'; export const getDefaults = (query: ExpressionQuery) => { switch (query.type) { diff --git a/public/app/features/folders/AccessControlFolderPermissions.tsx b/public/app/features/folders/AccessControlFolderPermissions.tsx index dac1954caab..376d3ff6f09 100644 --- a/public/app/features/folders/AccessControlFolderPermissions.tsx +++ b/public/app/features/folders/AccessControlFolderPermissions.tsx @@ -1,14 +1,15 @@ import React, { useEffect } from 'react'; -import { Permissions } from 'app/core/components/AccessControl'; import { connect, ConnectedProps } from 'react-redux'; +import { Permissions } from 'app/core/components/AccessControl'; import Page from 'app/core/components/Page/Page'; -import { getNavModel } from 'app/core/selectors/navModel'; import { contextSrv } from 'app/core/core'; -import { getLoadingNav } from './state/navModel'; -import { AccessControlAction, StoreState } from 'app/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { AccessControlAction, StoreState } from 'app/types'; + import { getFolderByUid } from './state/actions'; +import { getLoadingNav } from './state/navModel'; interface RouteProps extends GrafanaRouteComponentProps<{ uid: string }> {} diff --git a/public/app/features/folders/FolderAlerting.tsx b/public/app/features/folders/FolderAlerting.tsx index c1ea801a3ec..939676a751c 100644 --- a/public/app/features/folders/FolderAlerting.tsx +++ b/public/app/features/folders/FolderAlerting.tsx @@ -1,11 +1,14 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useAsync } from 'react-use'; + import Page from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useAsync } from 'react-use'; + import { AlertsFolderView } from '../alerting/unified/AlertsFolderView'; + import { getFolderByUid } from './state/actions'; import { getLoadingNav } from './state/navModel'; diff --git a/public/app/features/folders/FolderLibraryPanelsPage.tsx b/public/app/features/folders/FolderLibraryPanelsPage.tsx index 43443bdee6f..aa27c268d76 100644 --- a/public/app/features/folders/FolderLibraryPanelsPage.tsx +++ b/public/app/features/folders/FolderLibraryPanelsPage.tsx @@ -2,15 +2,16 @@ import React, { useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useAsync } from 'react-use'; -import { GrafanaRouteComponentProps } from '../../core/navigation/types'; -import { StoreState } from '../../types'; -import { getNavModel } from '../../core/selectors/navModel'; -import { getLoadingNav } from './state/navModel'; -import { LibraryElementDTO } from '../library-panels/types'; import Page from '../../core/components/Page/Page'; +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; +import { getNavModel } from '../../core/selectors/navModel'; +import { StoreState } from '../../types'; import { LibraryPanelsSearch } from '../library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch'; import { OpenLibraryPanelModal } from '../library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal'; +import { LibraryElementDTO } from '../library-panels/types'; + import { getFolderByUid } from './state/actions'; +import { getLoadingNav } from './state/navModel'; export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} diff --git a/public/app/features/folders/FolderPermissions.tsx b/public/app/features/folders/FolderPermissions.tsx index 70f249a994e..ed69935fa4f 100644 --- a/public/app/features/folders/FolderPermissions.tsx +++ b/public/app/features/folders/FolderPermissions.tsx @@ -1,11 +1,17 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import Page from 'app/core/components/Page/Page'; + import { Tooltip, Icon, Button } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; +import Page from 'app/core/components/Page/Page'; +import AddPermission from 'app/core/components/PermissionList/AddPermission'; +import PermissionList from 'app/core/components/PermissionList/PermissionList'; +import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl'; + import { getFolderByUid, getFolderPermissions, @@ -14,10 +20,6 @@ import { addFolderPermission, } from './state/actions'; import { getLoadingNav } from './state/navModel'; -import PermissionList from 'app/core/components/PermissionList/PermissionList'; -import AddPermission from 'app/core/components/PermissionList/AddPermission'; -import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} diff --git a/public/app/features/folders/FolderSettingsPage.test.tsx b/public/app/features/folders/FolderSettingsPage.test.tsx index 397e8380521..65f024996d3 100644 --- a/public/app/features/folders/FolderSettingsPage.test.tsx +++ b/public/app/features/folders/FolderSettingsPage.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; -import { FolderSettingsPage, Props } from './FolderSettingsPage'; import { shallow } from 'enzyme'; -import { NavModel } from '@grafana/data'; +import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; -import { setFolderTitle } from './state/reducers'; + +import { NavModel } from '@grafana/data'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { FolderSettingsPage, Props } from './FolderSettingsPage'; +import { setFolderTitle } from './state/reducers'; + const setup = (propOverrides?: object) => { const props: Props = { ...getRouteComponentProps(), diff --git a/public/app/features/folders/FolderSettingsPage.tsx b/public/app/features/folders/FolderSettingsPage.tsx index bd3da44ba7e..16cf9a0392a 100644 --- a/public/app/features/folders/FolderSettingsPage.tsx +++ b/public/app/features/folders/FolderSettingsPage.tsx @@ -1,16 +1,19 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { Button, LegacyForms } from '@grafana/ui'; const { Input } = LegacyForms; -import Page from 'app/core/components/Page/Page'; import appEvents from 'app/core/app_events'; +import Page from 'app/core/components/Page/Page'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; + +import { ShowConfirmModalEvent } from '../../types/events'; + import { deleteFolder, getFolderByUid, saveFolder } from './state/actions'; import { getLoadingNav } from './state/navModel'; import { setFolderTitle } from './state/reducers'; -import { ShowConfirmModalEvent } from '../../types/events'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; export interface OwnProps extends GrafanaRouteComponentProps<{ uid: string }> {} diff --git a/public/app/features/folders/components/NewDashboardsFolder.tsx b/public/app/features/folders/components/NewDashboardsFolder.tsx index a3ef23bf5bc..72617c8fbfe 100644 --- a/public/app/features/folders/components/NewDashboardsFolder.tsx +++ b/public/app/features/folders/components/NewDashboardsFolder.tsx @@ -1,11 +1,13 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { Button, Input, Form, Field } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; -import { createNewFolder } from '../state/actions'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; + import { validationSrv } from '../../manage-dashboards/services/ValidationSrv'; +import { createNewFolder } from '../state/actions'; const mapStateToProps = (state: StoreState) => ({ navModel: getNavModel(state.navIndex, 'manage-dashboards'), diff --git a/public/app/features/folders/state/actions.test.ts b/public/app/features/folders/state/actions.test.ts index 07b40871d5f..dd2d35e822b 100644 --- a/public/app/features/folders/state/actions.test.ts +++ b/public/app/features/folders/state/actions.test.ts @@ -1,11 +1,13 @@ import { Observable, of, throwError } from 'rxjs'; import { thunkTester } from 'test/core/thunk/thunkTester'; -import { checkFolderPermissions } from './actions'; -import { setCanViewFolderPermissions } from './reducers'; -import { backendSrv } from 'app/core/services/backend_srv'; + +import { FetchResponse } from '@grafana/runtime'; import { notifyApp } from 'app/core/actions'; import { createWarningNotification } from 'app/core/copy/appNotification'; -import { FetchResponse } from '@grafana/runtime'; +import { backendSrv } from 'app/core/services/backend_srv'; + +import { checkFolderPermissions } from './actions'; +import { setCanViewFolderPermissions } from './reducers'; describe('folder actions', () => { let fetchSpy: jest.SpyInstance>>; @@ -55,6 +57,7 @@ describe('folder actions', () => { createWarningNotification('Error checking folder permissions', 'Server error') ); notificationAction.payload.id = expect.any(String); + notificationAction.payload.timestamp = expect.any(Number); expect(dispatchedActions).toEqual([ expect.objectContaining(notificationAction), diff --git a/public/app/features/folders/state/actions.ts b/public/app/features/folders/state/actions.ts index 4d6fa6548a5..1a754a5a8c7 100644 --- a/public/app/features/folders/state/actions.ts +++ b/public/app/features/folders/state/actions.ts @@ -1,3 +1,5 @@ +import { lastValueFrom } from 'rxjs'; + import { locationUtil } from '@grafana/data'; import { getBackendSrv, locationService } from '@grafana/runtime'; import { notifyApp, updateNavIndex } from 'app/core/actions'; @@ -6,7 +8,7 @@ import { contextSrv } from 'app/core/core'; import { backendSrv } from 'app/core/services/backend_srv'; import { FolderState, ThunkResult } from 'app/types'; import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel } from 'app/types/acl'; -import { lastValueFrom } from 'rxjs'; + import { buildNavModel } from './navModel'; import { loadFolder, loadFolderPermissions, setCanViewFolderPermissions } from './reducers'; diff --git a/public/app/features/folders/state/navModel.ts b/public/app/features/folders/state/navModel.ts index 7f4a04e9b88..887d6c6a17a 100644 --- a/public/app/features/folders/state/navModel.ts +++ b/public/app/features/folders/state/navModel.ts @@ -1,6 +1,5 @@ import { NavModel, NavModelItem } from '@grafana/data'; import { contextSrv } from 'app/core/services/context_srv'; - import { AccessControlAction, FolderDTO } from 'app/types'; export function buildNavModel(folder: FolderDTO): NavModelItem { diff --git a/public/app/features/folders/state/reducers.test.ts b/public/app/features/folders/state/reducers.test.ts index 041efafe131..f68a707a591 100644 --- a/public/app/features/folders/state/reducers.test.ts +++ b/public/app/features/folders/state/reducers.test.ts @@ -1,4 +1,7 @@ import { FolderDTO, FolderState, OrgRole, PermissionLevel } from 'app/types'; + +import { reducerTester } from '../../../../test/core/redux/reducerTester'; + import { folderReducer, initialState, @@ -7,7 +10,6 @@ import { setCanViewFolderPermissions, setFolderTitle, } from './reducers'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; function getTestFolder(): FolderDTO { return { diff --git a/public/app/features/folders/state/reducers.ts b/public/app/features/folders/state/reducers.ts index 28fe7e10811..247fb5a84a0 100644 --- a/public/app/features/folders/state/reducers.ts +++ b/public/app/features/folders/state/reducers.ts @@ -1,7 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { DashboardAclDTO, FolderDTO, FolderState } from 'app/types'; import { processAclItems } from 'app/core/utils/acl'; +import { DashboardAclDTO, FolderDTO, FolderState } from 'app/types'; export const initialState: FolderState = { id: 0, diff --git a/public/app/features/geo/editor/GazetteerPathEditor.tsx b/public/app/features/geo/editor/GazetteerPathEditor.tsx index 2e4cf969e40..6a566193d7f 100644 --- a/public/app/features/geo/editor/GazetteerPathEditor.tsx +++ b/public/app/features/geo/editor/GazetteerPathEditor.tsx @@ -1,8 +1,10 @@ +import { css } from '@emotion/css'; import React, { FC, useMemo, useState, useEffect } from 'react'; + import { StandardEditorProps, SelectableValue, GrafanaTheme2 } from '@grafana/data'; import { Alert, Select, stylesFactory, useTheme2 } from '@grafana/ui'; + import { COUNTRIES_GAZETTEER_PATH, Gazetteer, getGazetteer } from '../gazetteer/gazetteer'; -import { css } from '@emotion/css'; const defaultPaths: Array> = [ { diff --git a/public/app/features/geo/format/geojson.test.ts b/public/app/features/geo/format/geojson.test.ts index 3f15737972c..fe00ba66035 100644 --- a/public/app/features/geo/format/geojson.test.ts +++ b/public/app/features/geo/format/geojson.test.ts @@ -1,4 +1,5 @@ import { dataFrameToJSON, FieldType } from '@grafana/data'; + import { frameFromGeoJSON } from './geojson'; describe('Read GeoJSON', () => { diff --git a/public/app/features/geo/format/geojson.ts b/public/app/features/geo/format/geojson.ts index 6811c6a94a4..447055c5fb0 100644 --- a/public/app/features/geo/format/geojson.ts +++ b/public/app/features/geo/format/geojson.ts @@ -1,7 +1,8 @@ -import { ArrayVector, DataFrame, Field, FieldType, getFieldTypeFromValue } from '@grafana/data'; import GeoJSON from 'ol/format/GeoJSON'; import { Geometry } from 'ol/geom'; +import { ArrayVector, DataFrame, Field, FieldType, getFieldTypeFromValue } from '@grafana/data'; + interface FieldInfo { values: any[]; types: Set; diff --git a/public/app/features/geo/format/utils.ts b/public/app/features/geo/format/utils.ts index e3d9149d70e..babba1cde80 100644 --- a/public/app/features/geo/format/utils.ts +++ b/public/app/features/geo/format/utils.ts @@ -1,8 +1,11 @@ -import { ArrayVector, Field, FieldConfig, FieldType } from '@grafana/data'; -import { getCenterPoint } from 'app/features/transformers/spatial/utils'; import { Geometry, GeometryCollection, LineString, Point } from 'ol/geom'; import { fromLonLat } from 'ol/proj'; + +import { ArrayVector, Field, FieldConfig, FieldType } from '@grafana/data'; +import { getCenterPoint } from 'app/features/transformers/spatial/utils'; + import { Gazetteer } from '../gazetteer/gazetteer'; + import { decodeGeohash } from './geohash'; export function pointFieldFromGeohash(geohash: Field): Field { diff --git a/public/app/features/geo/gazetteer/gazetteer.test.ts b/public/app/features/geo/gazetteer/gazetteer.test.ts index 7bff83cfb1c..f91d7d34fbd 100644 --- a/public/app/features/geo/gazetteer/gazetteer.test.ts +++ b/public/app/features/geo/gazetteer/gazetteer.test.ts @@ -1,4 +1,5 @@ import { getCenterPointWGS84 } from 'app/features/transformers/spatial/utils'; + import { getGazetteer } from './gazetteer'; let backendResults: any = { hello: 'world' }; diff --git a/public/app/features/geo/gazetteer/gazetteer.ts b/public/app/features/geo/gazetteer/gazetteer.ts index 9a6418cc95b..94b2b81a96a 100644 --- a/public/app/features/geo/gazetteer/gazetteer.ts +++ b/public/app/features/geo/gazetteer/gazetteer.ts @@ -1,10 +1,13 @@ +import { getCenter } from 'ol/extent'; +import { Geometry, Point } from 'ol/geom'; + import { DataFrame, Field, FieldType, KeyValue, toDataFrame } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import { loadWorldmapPoints } from './worldmap'; -import { Geometry, Point } from 'ol/geom'; + import { frameFromGeoJSON } from '../format/geojson'; import { pointFieldFromLonLat, pointFieldFromGeohash } from '../format/utils'; -import { getCenter } from 'ol/extent'; + +import { loadWorldmapPoints } from './worldmap'; export interface PlacenameInfo { point: () => Point | undefined; // lon, lat (WGS84) diff --git a/public/app/features/geo/gazetteer/worldmap.test.ts b/public/app/features/geo/gazetteer/worldmap.test.ts index 61b14ff94f3..a43471f7363 100644 --- a/public/app/features/geo/gazetteer/worldmap.test.ts +++ b/public/app/features/geo/gazetteer/worldmap.test.ts @@ -1,8 +1,10 @@ +import { toLonLat } from 'ol/proj'; + +import countriesJSON from '../../../../gazetteer/countries.json'; + import { getGazetteer } from './gazetteer'; let backendResults: any = { hello: 'world' }; -import countriesJSON from '../../../../gazetteer/countries.json'; -import { toLonLat } from 'ol/proj'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/features/geo/gazetteer/worldmap.ts b/public/app/features/geo/gazetteer/worldmap.ts index 799fe7c64ce..17a63b4823d 100644 --- a/public/app/features/geo/gazetteer/worldmap.ts +++ b/public/app/features/geo/gazetteer/worldmap.ts @@ -1,7 +1,8 @@ -import { PlacenameInfo, Gazetteer } from './gazetteer'; import { Point } from 'ol/geom'; import { fromLonLat } from 'ol/proj'; +import { PlacenameInfo, Gazetteer } from './gazetteer'; + // https://github.com/grafana/worldmap-panel/blob/master/src/data/countries.json export interface WorldmapPoint { key?: string; diff --git a/public/app/features/geo/utils/frameVectorSource.ts b/public/app/features/geo/utils/frameVectorSource.ts index 393b40cc2f8..704af664887 100644 --- a/public/app/features/geo/utils/frameVectorSource.ts +++ b/public/app/features/geo/utils/frameVectorSource.ts @@ -1,7 +1,9 @@ -import { DataFrame } from '@grafana/data'; import { Feature } from 'ol'; import { Geometry } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; + +import { DataFrame } from '@grafana/data'; + import { getGeometryField, LocationFieldMatchers } from './location'; export interface FrameVectorSourceOptions {} diff --git a/public/app/features/geo/utils/location.test.ts b/public/app/features/geo/utils/location.test.ts index 3b3ef12754c..d816361d477 100644 --- a/public/app/features/geo/utils/location.test.ts +++ b/public/app/features/geo/utils/location.test.ts @@ -1,6 +1,8 @@ -import { toDataFrame, FieldType, FrameGeometrySourceMode } from '@grafana/data'; import { Point } from 'ol/geom'; import { toLonLat } from 'ol/proj'; + +import { toDataFrame, FieldType, FrameGeometrySourceMode } from '@grafana/data'; + import { getGeometryField, getLocationFields, getLocationMatchers } from './location'; const longitude = [0, -74.1]; diff --git a/public/app/features/geo/utils/location.ts b/public/app/features/geo/utils/location.ts index 22348e13423..c9f552fa1c2 100644 --- a/public/app/features/geo/utils/location.ts +++ b/public/app/features/geo/utils/location.ts @@ -1,3 +1,5 @@ +import { Geometry } from 'ol/geom'; + import { FrameGeometrySource, FrameGeometrySourceMode, @@ -9,9 +11,9 @@ import { getFieldDisplayName, FieldType, } from '@grafana/data'; -import { Geometry } from 'ol/geom'; -import { getGazetteer, Gazetteer } from '../gazetteer/gazetteer'; + import { getGeoFieldFromGazetteer, pointFieldFromGeohash, pointFieldFromLonLat } from '../format/utils'; +import { getGazetteer, Gazetteer } from '../gazetteer/gazetteer'; export type FieldFinder = (frame: DataFrame) => Field | undefined; diff --git a/public/app/features/inspector/DetailText.tsx b/public/app/features/inspector/DetailText.tsx index 887c041e5b4..73342a1b584 100644 --- a/public/app/features/inspector/DetailText.tsx +++ b/public/app/features/inspector/DetailText.tsx @@ -1,7 +1,8 @@ -import React, { FC } from 'react'; -import { useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { useStyles } from '@grafana/ui'; const getStyles = (theme: GrafanaTheme) => css` margin: 0; diff --git a/public/app/features/inspector/InspectDataOptions.tsx b/public/app/features/inspector/InspectDataOptions.tsx index 32339b03b1a..46553d87cbb 100644 --- a/public/app/features/inspector/InspectDataOptions.tsx +++ b/public/app/features/inspector/InspectDataOptions.tsx @@ -1,11 +1,13 @@ import React, { FC } from 'react'; + import { DataFrame, DataTransformerID, getFrameDisplayName, SelectableValue } from '@grafana/data'; import { Field, HorizontalGroup, Select, Switch, VerticalGroup } from '@grafana/ui'; -import { getPanelInspectorStyles } from './styles'; -import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { PanelModel } from 'app/features/dashboard/state'; import { DetailText } from 'app/features/inspector/DetailText'; +import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; + +import { getPanelInspectorStyles } from './styles'; interface Props { options: GetDataOptions; diff --git a/public/app/features/inspector/InspectDataTab.test.tsx b/public/app/features/inspector/InspectDataTab.test.tsx index 5e8459b9ea1..273fd164696 100644 --- a/public/app/features/inspector/InspectDataTab.test.tsx +++ b/public/app/features/inspector/InspectDataTab.test.tsx @@ -1,7 +1,9 @@ -import React, { ComponentProps } from 'react'; -import { DataFrame, FieldType } from '@grafana/data'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React, { ComponentProps } from 'react'; + +import { DataFrame, FieldType } from '@grafana/data'; + import { InspectDataTab } from './InspectDataTab'; // the mock below gets rid of this warning from recompose: @@ -50,19 +52,19 @@ describe('InspectDataTab', () => { render(); expect(screen.getByText(/Data options/i)).toBeInTheDocument(); }); - it('should show available options', () => { + it('should show available options', async () => { render(); const dataOptions = screen.getByText(/Data options/i); - userEvent.click(dataOptions); + await userEvent.click(dataOptions); expect(screen.getByText(/Show data frame/i)).toBeInTheDocument(); expect(screen.getByText(/Download for Excel/i)).toBeInTheDocument(); }); - it('should show available dataFrame options', () => { + it('should show available dataFrame options', async () => { render(); const dataOptions = screen.getByText(/Data options/i); - userEvent.click(dataOptions); + await userEvent.click(dataOptions); const dataFrameInput = screen.getByRole('combobox', { name: /Select dataframe/i }); - userEvent.click(dataFrameInput); + await userEvent.click(dataFrameInput); expect(screen.getByText(/Second data frame/i)).toBeInTheDocument(); }); it('should show download logs button if logs data', () => { diff --git a/public/app/features/inspector/InspectDataTab.tsx b/public/app/features/inspector/InspectDataTab.tsx index 3f7a43e0c52..6fed6dbfdce 100644 --- a/public/app/features/inspector/InspectDataTab.tsx +++ b/public/app/features/inspector/InspectDataTab.tsx @@ -1,5 +1,8 @@ +import { css } from '@emotion/css'; +import { saveAs } from 'file-saver'; import React, { PureComponent } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; + import { applyFieldOverrides, applyRawFieldOverrides, @@ -14,19 +17,18 @@ import { transformDataFrame, TimeZone, } from '@grafana/data'; -import { Button, Spinner, Table } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; +import { Button, Spinner, Table } from '@grafana/ui'; +import { config } from 'app/core/config'; +import { dataFrameToLogsModel } from 'app/core/logs_model'; +import { PanelModel } from 'app/features/dashboard/state'; +import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; +import { transformToJaeger } from 'app/plugins/datasource/jaeger/responseTransform'; +import { transformToOTLP } from 'app/plugins/datasource/tempo/resultTransformer'; +import { transformToZipkin } from 'app/plugins/datasource/zipkin/utils/transforms'; + import { InspectDataOptions } from './InspectDataOptions'; import { getPanelInspectorStyles } from './styles'; -import { config } from 'app/core/config'; -import { saveAs } from 'file-saver'; -import { css } from '@emotion/css'; -import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; -import { PanelModel } from 'app/features/dashboard/state'; -import { dataFrameToLogsModel } from 'app/core/logs_model'; -import { transformToJaeger } from 'app/plugins/datasource/jaeger/responseTransform'; -import { transformToZipkin } from 'app/plugins/datasource/zipkin/utils/transforms'; -import { transformToOTLP } from 'app/plugins/datasource/tempo/resultTransformer'; interface Props { isLoading: boolean; diff --git a/public/app/features/inspector/InspectErrorTab.test.tsx b/public/app/features/inspector/InspectErrorTab.test.tsx index 07531dbb3e3..d86db092516 100644 --- a/public/app/features/inspector/InspectErrorTab.test.tsx +++ b/public/app/features/inspector/InspectErrorTab.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { InspectErrorTab } from './InspectErrorTab'; describe('InspectErrorTab', () => { diff --git a/public/app/features/inspector/InspectErrorTab.tsx b/public/app/features/inspector/InspectErrorTab.tsx index 55fe2d89cae..f3609b187f4 100644 --- a/public/app/features/inspector/InspectErrorTab.tsx +++ b/public/app/features/inspector/InspectErrorTab.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataQueryError } from '@grafana/data'; import { JSONFormatter } from '@grafana/ui'; diff --git a/public/app/features/inspector/InspectJSONTab.tsx b/public/app/features/inspector/InspectJSONTab.tsx index acc6d4b3c95..b0ec6db4f52 100644 --- a/public/app/features/inspector/InspectJSONTab.tsx +++ b/public/app/features/inspector/InspectJSONTab.tsx @@ -1,10 +1,12 @@ import React, { PureComponent } from 'react'; -import { AppEvents, dataFrameToJSON, PanelData, SelectableValue } from '@grafana/data'; -import { Button, CodeEditor, Field, Select } from '@grafana/ui'; import AutoSizer from 'react-virtualized-auto-sizer'; + +import { AppEvents, dataFrameToJSON, PanelData, SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { Button, CodeEditor, Field, Select } from '@grafana/ui'; import { appEvents } from 'app/core/core'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; + import { getPanelInspectorStyles } from '../inspector/styles'; enum ShowContent { diff --git a/public/app/features/inspector/InspectMetadataTab.tsx b/public/app/features/inspector/InspectMetadataTab.tsx index a3ab5bda303..cbb7bcfa186 100644 --- a/public/app/features/inspector/InspectMetadataTab.tsx +++ b/public/app/features/inspector/InspectMetadataTab.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataSourceApi, PanelData } from '@grafana/data'; interface InspectMetadataTabProps { diff --git a/public/app/features/inspector/InspectStatsTab.tsx b/public/app/features/inspector/InspectStatsTab.tsx index d6508f0e736..22290245b52 100644 --- a/public/app/features/inspector/InspectStatsTab.tsx +++ b/public/app/features/inspector/InspectStatsTab.tsx @@ -1,7 +1,9 @@ +import React from 'react'; + import { PanelData, QueryResultMetaStat, TimeZone } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; + import { InspectStatsTable } from './InspectStatsTable'; -import React from 'react'; interface InspectStatsTabProps { data: PanelData; diff --git a/public/app/features/inspector/InspectStatsTable.tsx b/public/app/features/inspector/InspectStatsTable.tsx index 52dfc62b77c..cc1c5ba6665 100644 --- a/public/app/features/inspector/InspectStatsTable.tsx +++ b/public/app/features/inspector/InspectStatsTable.tsx @@ -1,4 +1,6 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { FieldType, formattedValueToString, @@ -8,7 +10,6 @@ import { TimeZone, } from '@grafana/data'; import { stylesFactory, useTheme2 } from '@grafana/ui'; -import { css } from '@emotion/css'; interface InspectStatsTableProps { timeZone: TimeZone; diff --git a/public/app/features/inspector/QueryInspector.tsx b/public/app/features/inspector/QueryInspector.tsx index 33bffef9592..3fd492e1dfc 100644 --- a/public/app/features/inspector/QueryInspector.tsx +++ b/public/app/features/inspector/QueryInspector.tsx @@ -1,17 +1,18 @@ -import React, { PureComponent } from 'react'; -import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; -import { AppEvents, DataFrame } from '@grafana/data'; - -import appEvents from 'app/core/app_events'; -import { PanelModel } from 'app/features/dashboard/state'; -import { getPanelInspectorStyles } from './styles'; -import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils'; -import { config, RefreshEvent } from '@grafana/runtime'; import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; import { Subscription } from 'rxjs'; -import { backendSrv } from 'app/core/services/backend_srv'; + +import { AppEvents, DataFrame } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Stack } from '@grafana/experimental'; +import { config, RefreshEvent } from '@grafana/runtime'; +import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils'; +import { PanelModel } from 'app/features/dashboard/state'; + +import { getPanelInspectorStyles } from './styles'; interface DsQuery { isLoading: boolean; diff --git a/public/app/features/inspector/styles.ts b/public/app/features/inspector/styles.ts index 5e9873ceffd..123ccb131a7 100644 --- a/public/app/features/inspector/styles.ts +++ b/public/app/features/inspector/styles.ts @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; -import { config } from 'app/core/config'; + import { stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; export const getPanelInspectorStyles = stylesFactory(() => { return { @@ -25,6 +26,7 @@ export const getPanelInspectorStyles = stylesFactory(() => { content: css` flex-grow: 1; height: 100%; + overflow: scroll; `, editor: css` font-family: monospace; diff --git a/public/app/features/invites/InviteeRow.tsx b/public/app/features/invites/InviteeRow.tsx index 1f5007df263..51425cee932 100644 --- a/public/app/features/invites/InviteeRow.tsx +++ b/public/app/features/invites/InviteeRow.tsx @@ -1,8 +1,10 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { Invitee } from 'app/types'; -import { revokeInvite } from './state/actions'; + import { Button, ClipboardButton } from '@grafana/ui'; +import { Invitee } from 'app/types'; + +import { revokeInvite } from './state/actions'; const mapDispatchToProps = { revokeInvite, diff --git a/public/app/features/invites/InviteesTable.test.tsx b/public/app/features/invites/InviteesTable.test.tsx index e776b52e714..3cdc53b069b 100644 --- a/public/app/features/invites/InviteesTable.test.tsx +++ b/public/app/features/invites/InviteesTable.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import InviteesTable, { Props } from './InviteesTable'; +import React from 'react'; + import { Invitee } from 'app/types'; + import { getMockInvitees } from '../users/__mocks__/userMocks'; +import InviteesTable, { Props } from './InviteesTable'; + const setup = (propOverrides?: object) => { const props: Props = { invitees: [] as Invitee[], diff --git a/public/app/features/invites/InviteesTable.tsx b/public/app/features/invites/InviteesTable.tsx index fa5fd087a2f..3720c239fac 100644 --- a/public/app/features/invites/InviteesTable.tsx +++ b/public/app/features/invites/InviteesTable.tsx @@ -1,5 +1,7 @@ import React, { PureComponent } from 'react'; + import { Invitee } from 'app/types'; + import InviteeRow from './InviteeRow'; export interface Props { diff --git a/public/app/features/invites/SignupInvited.test.tsx b/public/app/features/invites/SignupInvited.test.tsx index e5ed872e224..d16170371e9 100644 --- a/public/app/features/invites/SignupInvited.test.tsx +++ b/public/app/features/invites/SignupInvited.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; + +import { backendSrv } from '../../core/services/backend_srv'; import { SignupInvitedPage, Props } from './SignupInvited'; -import { backendSrv } from '../../core/services/backend_srv'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; jest.mock('app/core/core', () => ({ contextSrv: { @@ -103,7 +105,7 @@ describe('SignupInvitedPage', () => { it('then required fields should show error messages and nothing should be posted', async () => { const { postSpy } = await setupTestContext({ get: { email: '', invitedBy: '', name: '', username: '' } }); - userEvent.click(screen.getByRole('button', { name: /sign up/i })); + await userEvent.click(screen.getByRole('button', { name: /sign up/i })); await waitFor(() => expect(screen.getByText(/email is required/i)).toBeInTheDocument()); expect(screen.getByText(/username is required/i)).toBeInTheDocument(); @@ -116,8 +118,8 @@ describe('SignupInvitedPage', () => { it('then correct form data should be posted', async () => { const { postSpy } = await setupTestContext(); - userEvent.type(screen.getByPlaceholderText(/password/i), 'pass@word1'); - userEvent.click(screen.getByRole('button', { name: /sign up/i })); + await userEvent.type(screen.getByPlaceholderText(/password/i), 'pass@word1'); + await userEvent.click(screen.getByRole('button', { name: /sign up/i })); await waitFor(() => expect(postSpy).toHaveBeenCalledTimes(1)); expect(postSpy).toHaveBeenCalledWith('/api/user/invite/complete', { diff --git a/public/app/features/invites/SignupInvited.tsx b/public/app/features/invites/SignupInvited.tsx index 57cfe41a5df..4d862d6377d 100644 --- a/public/app/features/invites/SignupInvited.tsx +++ b/public/app/features/invites/SignupInvited.tsx @@ -1,10 +1,11 @@ import React, { FC, useState } from 'react'; +import { useAsync } from 'react-use'; + import { getBackendSrv } from '@grafana/runtime'; import { Button, Field, Form, Input } from '@grafana/ui'; -import { useAsync } from 'react-use'; import Page from 'app/core/components/Page/Page'; -import { contextSrv } from 'app/core/core'; import { getConfig } from 'app/core/config'; +import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; interface FormModel { diff --git a/public/app/features/invites/state/reducers.test.ts b/public/app/features/invites/state/reducers.test.ts index e4563709220..9ac92e88f68 100644 --- a/public/app/features/invites/state/reducers.test.ts +++ b/public/app/features/invites/state/reducers.test.ts @@ -1,9 +1,12 @@ import { keyBy } from 'lodash'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { initialState, invitesReducer } from './reducers'; -import { fetchInvitees, revokeInvite } from './actions'; + import { getMockInvitees } from 'app/features/users/__mocks__/userMocks'; +import { reducerTester } from '../../../../test/core/redux/reducerTester'; + +import { fetchInvitees, revokeInvite } from './actions'; +import { initialState, invitesReducer } from './reducers'; + describe('inviteesReducer', () => { describe('when fetchInvitees is dispatched', () => { it('then state should be correct', () => { diff --git a/public/app/features/invites/state/reducers.ts b/public/app/features/invites/state/reducers.ts index bad26fdc938..35ac54eeca0 100644 --- a/public/app/features/invites/state/reducers.ts +++ b/public/app/features/invites/state/reducers.ts @@ -1,5 +1,7 @@ import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; + import { Invitee } from 'app/types'; + import { fetchInvitees, revokeInvite } from './actions'; export type Status = 'idle' | 'loading' | 'succeeded' | 'failed'; diff --git a/public/app/features/invites/state/selectors.ts b/public/app/features/invites/state/selectors.ts index 7a9468dbde9..daf42e464bc 100644 --- a/public/app/features/invites/state/selectors.ts +++ b/public/app/features/invites/state/selectors.ts @@ -1,4 +1,5 @@ import { createSelector } from '@reduxjs/toolkit'; + import { selectors } from './reducers'; export const { selectAll, selectById, selectTotal } = selectors; diff --git a/public/app/features/library-panels/LibraryPanelsPage.tsx b/public/app/features/library-panels/LibraryPanelsPage.tsx index e13d5937643..09342b0e5cf 100644 --- a/public/app/features/library-panels/LibraryPanelsPage.tsx +++ b/public/app/features/library-panels/LibraryPanelsPage.tsx @@ -1,13 +1,14 @@ import React, { FC, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { GrafanaRouteComponentProps } from '../../core/navigation/types'; -import { StoreState } from '../../types'; -import { getNavModel } from '../../core/selectors/navModel'; import Page from '../../core/components/Page/Page'; +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; +import { getNavModel } from '../../core/selectors/navModel'; +import { StoreState } from '../../types'; + import { LibraryPanelsSearch } from './components/LibraryPanelsSearch/LibraryPanelsSearch'; -import { LibraryElementDTO } from './types'; import { OpenLibraryPanelModal } from './components/OpenLibraryPanelModal/OpenLibraryPanelModal'; +import { LibraryElementDTO } from './types'; const mapStateToProps = (state: StoreState) => ({ navModel: getNavModel(state.navIndex, 'library-panels'), diff --git a/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx b/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx index e8c12b97c3d..7173d22125f 100644 --- a/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx @@ -1,10 +1,12 @@ import React, { useCallback, useEffect, useState } from 'react'; +import { useAsync, useDebounce } from 'react-use'; + import { Button, Field, Input, Modal } from '@grafana/ui'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; + import { PanelModel } from '../../../dashboard/state'; -import { usePanelSave } from '../../utils/usePanelSave'; -import { useAsync, useDebounce } from 'react-use'; import { getLibraryPanelByName } from '../../state/api'; +import { usePanelSave } from '../../utils/usePanelSave'; interface AddLibraryPanelContentsProps { onDismiss: () => void; diff --git a/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx b/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx index c6b7697c2f4..f42ab49b6bb 100644 --- a/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/ChangeLibraryPanelModal/ChangeLibraryPanelModal.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { ConfirmModal } from '@grafana/ui'; import { PanelModel } from '../../../dashboard/state'; diff --git a/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx b/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx index 0f2da652370..5ef9b35e88d 100644 --- a/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/DeleteLibraryPanelModal/DeleteLibraryPanelModal.tsx @@ -1,12 +1,14 @@ import React, { FC, useEffect, useMemo, useReducer } from 'react'; -import { Button, Modal, useStyles } from '@grafana/ui'; -import { LoadingState } from '@grafana/data'; +import { LoadingState } from '@grafana/data'; +import { Button, Modal, useStyles } from '@grafana/ui'; + +import { getModalStyles } from '../../styles'; import { LibraryElementDTO } from '../../types'; import { asyncDispatcher } from '../LibraryPanelsView/actions'; -import { deleteLibraryPanelModalReducer, initialDeleteLibraryPanelModalState } from './reducer'; + import { getConnectedDashboards } from './actions'; -import { getModalStyles } from '../../styles'; +import { deleteLibraryPanelModalReducer, initialDeleteLibraryPanelModalState } from './reducer'; interface Props { libraryPanel: LibraryElementDTO; diff --git a/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts b/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts index 0d7784495c8..5b8e2e18911 100644 --- a/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts +++ b/public/app/features/library-panels/components/DeleteLibraryPanelModal/actions.ts @@ -1,5 +1,6 @@ -import { DispatchResult, LibraryElementDTO } from '../../types'; import { getConnectedDashboards as apiGetConnectedDashboards } from '../../state/api'; +import { DispatchResult, LibraryElementDTO } from '../../types'; + import { searchCompleted } from './reducer'; export function getConnectedDashboards(libraryPanel: LibraryElementDTO): DispatchResult { diff --git a/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.test.ts b/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.test.ts index 78d9265edce..f2604303d3f 100644 --- a/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.test.ts +++ b/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.test.ts @@ -1,11 +1,13 @@ +import { LoadingState } from '@grafana/data'; + import { reducerTester } from '../../../../../test/core/redux/reducerTester'; + import { deleteLibraryPanelModalReducer, DeleteLibraryPanelModalState, initialDeleteLibraryPanelModalState, searchCompleted, } from './reducer'; -import { LoadingState } from '@grafana/data'; describe('deleteLibraryPanelModalReducer', () => { describe('when created', () => { diff --git a/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.ts b/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.ts index 091b4f499de..0f019572df4 100644 --- a/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.ts +++ b/public/app/features/library-panels/components/DeleteLibraryPanelModal/reducer.ts @@ -1,7 +1,8 @@ -import { DashboardSearchHit } from 'app/features/search/types'; -import { LoadingState } from '@grafana/data'; -import { AnyAction } from 'redux'; import { createAction } from '@reduxjs/toolkit'; +import { AnyAction } from 'redux'; + +import { LoadingState } from '@grafana/data'; +import { DashboardSearchHit } from 'app/features/search/types'; export interface DeleteLibraryPanelModalState { loadingState: LoadingState; diff --git a/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx b/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx index a1578e21014..1717ab7c313 100644 --- a/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx +++ b/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx @@ -1,12 +1,14 @@ -import React, { ReactElement, useState } from 'react'; import { css } from '@emotion/css'; +import React, { ReactElement, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { Icon, Link, useStyles2 } from '@grafana/ui'; -import { LibraryElementDTO } from '../../types'; -import { PanelTypeCard } from 'app/features/panel/components/VizTypePicker/PanelTypeCard'; -import { DeleteLibraryPanelModal } from '../DeleteLibraryPanelModal/DeleteLibraryPanelModal'; import { config } from '@grafana/runtime'; +import { Icon, Link, useStyles2 } from '@grafana/ui'; import { getPanelPluginNotFound } from 'app/features/panel/components/PanelPluginError'; +import { PanelTypeCard } from 'app/features/panel/components/VizTypePicker/PanelTypeCard'; + +import { LibraryElementDTO } from '../../types'; +import { DeleteLibraryPanelModal } from '../DeleteLibraryPanelModal/DeleteLibraryPanelModal'; export interface LibraryPanelCardProps { libraryPanel: LibraryElementDTO; diff --git a/public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx b/public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx index 462f92fa0d0..57f472a9d33 100644 --- a/public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx +++ b/public/app/features/library-panels/components/LibraryPanelInfo/LibraryPanelInfo.tsx @@ -1,9 +1,11 @@ -import { DateTimeInput, GrafanaTheme } from '@grafana/data'; -import { useStyles } from '@grafana/ui'; import { css } from '@emotion/css'; import React from 'react'; -import { PanelModelWithLibraryPanel } from '../../types'; + +import { DateTimeInput, GrafanaTheme } from '@grafana/data'; +import { useStyles } from '@grafana/ui'; + import { isPanelModelLibraryPanel } from '../../guard'; +import { PanelModelWithLibraryPanel } from '../../types'; interface Props { panel: PanelModelWithLibraryPanel; diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index 5c9c4ca4b06..80244f7e6e5 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; import { within } from '@testing-library/dom'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { PanelPluginMeta, PluginType } from '@grafana/data'; -import { LibraryPanelsSearch, LibraryPanelsSearchProps } from './LibraryPanelsSearch'; -import * as api from '../../state/api'; -import { LibraryElementKind, LibraryElementsSearchResult } from '../../types'; import { backendSrv } from '../../../../core/services/backend_srv'; import * as panelUtils from '../../../panel/state/util'; +import * as api from '../../state/api'; +import { LibraryElementKind, LibraryElementsSearchResult } from '../../types'; + +import { LibraryPanelsSearch, LibraryPanelsSearchProps } from './LibraryPanelsSearch'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), @@ -75,6 +77,7 @@ async function getTestContext( await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalled()); expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1); + jest.clearAllMocks(); return { rerender, getLibraryPanelsSpy, getSpy, getAllPanelPluginMetaSpy }; } @@ -91,18 +94,18 @@ describe('LibraryPanelsSearch', () => { describe('and user searches for library panel by name or description', () => { it('should call api with correct params', async () => { const { getLibraryPanelsSpy } = await getTestContext(); - getLibraryPanelsSpy.mockClear(); - userEvent.type(screen.getByPlaceholderText(/search by name/i), 'a'); + await userEvent.type(screen.getByPlaceholderText(/search by name/i), 'a'); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalled()); - expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1); - expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ - searchString: 'a', - folderFilter: [], - page: 0, - typeFilter: [], - perPage: 40, - }); + await waitFor(() => + expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ + searchString: 'a', + folderFilter: [], + page: 0, + typeFilter: [], + perPage: 40, + }) + ); }); }); }); @@ -119,18 +122,18 @@ describe('LibraryPanelsSearch', () => { describe('and user changes sorting', () => { it('should call api with correct params', async () => { const { getLibraryPanelsSpy } = await getTestContext({ showSort: true }); - getLibraryPanelsSpy.mockClear(); - userEvent.type(screen.getByText(/sort \(default a–z\)/i), 'Desc{enter}'); - await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1)); - expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ - searchString: '', - sortDirection: 'alpha-desc', - folderFilter: [], - page: 0, - typeFilter: [], - perPage: 40, - }); + await userEvent.type(screen.getByText(/sort \(default a–z\)/i), 'Desc{enter}'); + await waitFor(() => + expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ + searchString: '', + sortDirection: 'alpha-desc', + folderFilter: [], + page: 0, + typeFilter: [], + perPage: 40, + }) + ); }); }); }); @@ -147,18 +150,18 @@ describe('LibraryPanelsSearch', () => { describe('and user changes panel filter', () => { it('should call api with correct params', async () => { const { getLibraryPanelsSpy } = await getTestContext({ showPanelFilter: true }); - getLibraryPanelsSpy.mockClear(); - userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Graph{enter}'); - userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Time Series{enter}'); - await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1)); - expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ - searchString: '', - folderFilter: [], - page: 0, - typeFilter: ['graph', 'timeseries'], - perPage: 40, - }); + await userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Graph{enter}'); + await userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Time Series{enter}'); + await waitFor(() => + expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ + searchString: '', + folderFilter: [], + page: 0, + typeFilter: ['graph', 'timeseries'], + perPage: 40, + }) + ); }); }); }); @@ -175,20 +178,20 @@ describe('LibraryPanelsSearch', () => { describe('and user changes folder filter', () => { it('should call api with correct params', async () => { const { getLibraryPanelsSpy } = await getTestContext({ showFolderFilter: true }); - getLibraryPanelsSpy.mockClear(); - userEvent.click(screen.getByRole('combobox', { name: /folder filter/i })); - userEvent.type(screen.getByRole('combobox', { name: /folder filter/i }), '{enter}', { + await userEvent.click(screen.getByRole('combobox', { name: /folder filter/i })); + await userEvent.type(screen.getByRole('combobox', { name: /folder filter/i }), '{enter}', { skipClick: true, }); - await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1)); - expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ - searchString: '', - folderFilter: ['0'], - page: 0, - typeFilter: [], - perPage: 40, - }); + await waitFor(() => + expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ + searchString: '', + folderFilter: ['0'], + page: 0, + typeFilter: [], + perPage: 40, + }) + ); }); }); }); diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx index 728db2ca9e3..fcd6a5a8086 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx @@ -1,14 +1,17 @@ -import React, { useReducer } from 'react'; -import { HorizontalGroup, useStyles2, VerticalGroup, FilterInput } from '@grafana/ui'; -import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; import { css } from '@emotion/css'; -import { SortPicker } from '../../../../core/components/Select/SortPicker'; -import { PanelTypeFilter } from '../../../../core/components/PanelTypeFilter/PanelTypeFilter'; -import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView'; -import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../core/constants'; -import { LibraryElementDTO } from '../../types'; +import React, { useReducer } from 'react'; + +import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; +import { HorizontalGroup, useStyles2, VerticalGroup, FilterInput } from '@grafana/ui'; + import { FolderFilter } from '../../../../core/components/FolderFilter/FolderFilter'; +import { PanelTypeFilter } from '../../../../core/components/PanelTypeFilter/PanelTypeFilter'; +import { SortPicker } from '../../../../core/components/Select/SortPicker'; +import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../core/constants'; import { FolderInfo } from '../../../../types'; +import { LibraryElementDTO } from '../../types'; +import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView'; + import { folderFilterChanged, initialLibraryPanelsSearchState, diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.test.ts b/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.test.ts index e887a5588b6..b6d9f5cb989 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.test.ts +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.test.ts @@ -1,4 +1,5 @@ import { reducerTester } from '../../../../../test/core/redux/reducerTester'; + import { folderFilterChanged, initialLibraryPanelsSearchState, diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.ts b/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.ts index 1a81e65d427..0e009bb76d8 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.ts +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/reducer.ts @@ -1,5 +1,6 @@ -import { AnyAction } from 'redux'; import { createAction } from '@reduxjs/toolkit'; +import { AnyAction } from 'redux'; + import { PanelPluginMeta, SelectableValue } from '@grafana/data'; import { FolderInfo } from '../../../../types'; diff --git a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx index cecfec6d7be..c3b7c9dd866 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx @@ -1,13 +1,15 @@ +import { css, cx } from '@emotion/css'; import React, { useMemo, useReducer } from 'react'; import { useDebounce } from 'react-use'; -import { css, cx } from '@emotion/css'; -import { Pagination, useStyles } from '@grafana/ui'; -import { GrafanaTheme, LoadingState } from '@grafana/data'; -import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard'; +import { GrafanaTheme, LoadingState } from '@grafana/data'; +import { Pagination, useStyles } from '@grafana/ui'; + import { LibraryElementDTO } from '../../types'; -import { changePage, initialLibraryPanelsViewState, libraryPanelsViewReducer } from './reducer'; +import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard'; + import { asyncDispatcher, deleteLibraryPanel, searchForLibraryPanels } from './actions'; +import { changePage, initialLibraryPanelsViewState, libraryPanelsViewReducer } from './reducer'; interface LibraryPanelViewProps { className?: string; diff --git a/public/app/features/library-panels/components/LibraryPanelsView/actions.ts b/public/app/features/library-panels/components/LibraryPanelsView/actions.ts index 8dbd962787c..f304c318f22 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/actions.ts +++ b/public/app/features/library-panels/components/LibraryPanelsView/actions.ts @@ -1,9 +1,10 @@ -import { Dispatch } from 'react'; import { AnyAction } from '@reduxjs/toolkit'; +import { Dispatch } from 'react'; import { from, merge, of, Subscription, timer } from 'rxjs'; import { catchError, finalize, mapTo, mergeMap, share, takeUntil } from 'rxjs/operators'; import { deleteLibraryPanel as apiDeleteLibraryPanel, getLibraryPanels } from '../../state/api'; + import { initialLibraryPanelsViewState, initSearch, searchCompleted } from './reducer'; type DispatchResult = (dispatch: Dispatch) => void; diff --git a/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts b/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts index 9b9864d5bc3..be4cbad2922 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts +++ b/public/app/features/library-panels/components/LibraryPanelsView/reducer.test.ts @@ -1,6 +1,8 @@ import { LoadingState } from '@grafana/data'; import { reducerTester } from '../../../../../test/core/redux/reducerTester'; +import { LibraryElementDTO, LibraryElementKind } from '../../types'; + import { changePage, initialLibraryPanelsViewState, @@ -9,7 +11,6 @@ import { LibraryPanelsViewState, searchCompleted, } from './reducer'; -import { LibraryElementDTO, LibraryElementKind } from '../../types'; describe('libraryPanelsViewReducer', () => { describe('when initSearch is dispatched', () => { diff --git a/public/app/features/library-panels/components/LibraryPanelsView/reducer.ts b/public/app/features/library-panels/components/LibraryPanelsView/reducer.ts index 4d4726f0183..b72a261853c 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/reducer.ts +++ b/public/app/features/library-panels/components/LibraryPanelsView/reducer.ts @@ -1,8 +1,9 @@ import { createAction } from '@reduxjs/toolkit'; +import { AnyAction } from 'redux'; + import { LoadingState } from '@grafana/data'; import { LibraryElementDTO } from '../../types'; -import { AnyAction } from 'redux'; export interface LibraryPanelsViewState { loadingState: LoadingState; diff --git a/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx b/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx index 6d560282d29..6bb986fcfb0 100644 --- a/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/OpenLibraryPanelModal/OpenLibraryPanelModal.tsx @@ -1,13 +1,14 @@ -import React, { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/css'; -import { AsyncSelect, Button, Modal, useStyles2 } from '@grafana/ui'; +import { debounce } from 'lodash'; +import React, { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react'; + import { GrafanaTheme2, SelectableValue, urlUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; +import { AsyncSelect, Button, Modal, useStyles2 } from '@grafana/ui'; -import { LibraryElementDTO } from '../../types'; import { DashboardSearchHit } from '../../../search/types'; import { getConnectedDashboards, getLibraryPanelConnectedDashboards } from '../../state/api'; -import { debounce } from 'lodash'; +import { LibraryElementDTO } from '../../types'; export interface OpenLibraryPanelModalProps { onDismiss: () => void; diff --git a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx index c39c5b7beaf..f61cdb81276 100644 --- a/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx +++ b/public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx @@ -1,17 +1,18 @@ +import { css } from '@emotion/css'; import React, { FC, useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { css } from '@emotion/css'; + import { GrafanaTheme2, PanelPluginMeta } from '@grafana/data'; import { Button, useStyles2, VerticalGroup } from '@grafana/ui'; - -import { PanelModel } from 'app/features/dashboard/state'; -import { AddLibraryPanelModal } from '../AddLibraryPanelModal/AddLibraryPanelModal'; -import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView'; -import { LibraryElementDTO } from '../../types'; -import { changeToLibraryPanel } from 'app/features/panel/state/actions'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { ChangeLibraryPanelModal } from '../ChangeLibraryPanelModal/ChangeLibraryPanelModal'; +import { PanelModel } from 'app/features/dashboard/state'; +import { changeToLibraryPanel } from 'app/features/panel/state/actions'; + import { PanelTypeFilter } from '../../../../core/components/PanelTypeFilter/PanelTypeFilter'; +import { LibraryElementDTO } from '../../types'; +import { AddLibraryPanelModal } from '../AddLibraryPanelModal/AddLibraryPanelModal'; +import { ChangeLibraryPanelModal } from '../ChangeLibraryPanelModal/ChangeLibraryPanelModal'; +import { LibraryPanelsView } from '../LibraryPanelsView/LibraryPanelsView'; interface Props { panel: PanelModel; diff --git a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx index 6829868aea2..8806b2ab5ad 100644 --- a/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/SaveLibraryPanelModal/SaveLibraryPanelModal.tsx @@ -1,10 +1,12 @@ import React, { useCallback, useState } from 'react'; -import { Button, Icon, Input, Modal, useStyles } from '@grafana/ui'; import { useAsync, useDebounce } from 'react-use'; -import { usePanelSave } from '../../utils/usePanelSave'; + +import { Button, Icon, Input, Modal, useStyles } from '@grafana/ui'; + import { getConnectedDashboards } from '../../state/api'; -import { PanelModelWithLibraryPanel } from '../../types'; import { getModalStyles } from '../../styles'; +import { PanelModelWithLibraryPanel } from '../../types'; +import { usePanelSave } from '../../utils/usePanelSave'; interface Props { panel: PanelModelWithLibraryPanel; diff --git a/public/app/features/library-panels/components/UnlinkModal/UnlinkModal.tsx b/public/app/features/library-panels/components/UnlinkModal/UnlinkModal.tsx index ad342d8f83b..961c06f0dea 100644 --- a/public/app/features/library-panels/components/UnlinkModal/UnlinkModal.tsx +++ b/public/app/features/library-panels/components/UnlinkModal/UnlinkModal.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { ConfirmModal } from '@grafana/ui'; interface Props { diff --git a/public/app/features/library-panels/guard.ts b/public/app/features/library-panels/guard.ts index 713aefbf411..739fe7bd2e3 100644 --- a/public/app/features/library-panels/guard.ts +++ b/public/app/features/library-panels/guard.ts @@ -1,4 +1,5 @@ import { PanelModel } from '../dashboard/state'; + import { PanelModelWithLibraryPanel } from './types'; export function isPanelModelLibraryPanel(panel: PanelModel): panel is PanelModelWithLibraryPanel { diff --git a/public/app/features/library-panels/state/api.ts b/public/app/features/library-panels/state/api.ts index 6f70625da1e..8ea659de250 100644 --- a/public/app/features/library-panels/state/api.ts +++ b/public/app/features/library-panels/state/api.ts @@ -1,3 +1,7 @@ +import { lastValueFrom } from 'rxjs'; + +import { getBackendSrv } from '../../../core/services/backend_srv'; +import { DashboardSearchHit } from '../../search/types'; import { LibraryElementConnectionDTO, LibraryElementDTO, @@ -5,9 +9,6 @@ import { LibraryElementsSearchResult, PanelModelWithLibraryPanel, } from '../types'; -import { DashboardSearchHit } from '../../search/types'; -import { getBackendSrv } from '../../../core/services/backend_srv'; -import { lastValueFrom } from 'rxjs'; export interface GetLibraryPanelsOptions { searchString?: string; diff --git a/public/app/features/library-panels/styles.ts b/public/app/features/library-panels/styles.ts index 3c26f7a1876..b76b2e7b43e 100644 --- a/public/app/features/library-panels/styles.ts +++ b/public/app/features/library-panels/styles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; + import { GrafanaTheme } from '@grafana/data'; export function getModalStyles(theme: GrafanaTheme) { diff --git a/public/app/features/library-panels/types.ts b/public/app/features/library-panels/types.ts index b523cdca8d7..8991ce02872 100644 --- a/public/app/features/library-panels/types.ts +++ b/public/app/features/library-panels/types.ts @@ -1,6 +1,7 @@ -import { PanelModel } from '../dashboard/state'; -import { Dispatch } from 'react'; import { AnyAction } from '@reduxjs/toolkit'; +import { Dispatch } from 'react'; + +import { PanelModel } from '../dashboard/state'; export enum LibraryElementKind { Panel = 1, diff --git a/public/app/features/library-panels/utils.ts b/public/app/features/library-panels/utils.ts index 07ae681628e..c9e2b8a9bc6 100644 --- a/public/app/features/library-panels/utils.ts +++ b/public/app/features/library-panels/utils.ts @@ -1,8 +1,9 @@ -import { LibraryElementDTO, PanelModelLibraryPanel } from './types'; -import { PanelModel } from '../dashboard/state'; -import { addLibraryPanel, updateLibraryPanel } from './state/api'; import { createErrorNotification, createSuccessNotification } from '../../core/copy/appNotification'; import { AppNotification } from '../../types'; +import { PanelModel } from '../dashboard/state'; + +import { addLibraryPanel, updateLibraryPanel } from './state/api'; +import { LibraryElementDTO, PanelModelLibraryPanel } from './types'; export function createPanelLibraryErrorNotification(message: string): AppNotification { return createErrorNotification(message); diff --git a/public/app/features/library-panels/utils/usePanelSave.ts b/public/app/features/library-panels/utils/usePanelSave.ts index 436d2b255b7..90bb0e468a6 100644 --- a/public/app/features/library-panels/utils/usePanelSave.ts +++ b/public/app/features/library-panels/utils/usePanelSave.ts @@ -2,13 +2,14 @@ import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { notifyApp } from 'app/core/actions'; import { PanelModel } from 'app/features/dashboard/state'; + import { createPanelLibraryErrorNotification, createPanelLibrarySuccessNotification, saveAndRefreshLibraryPanel, } from '../utils'; -import { notifyApp } from 'app/core/actions'; export const usePanelSave = () => { const dispatch = useDispatch(); diff --git a/public/app/features/live/LiveConnectionWarning.tsx b/public/app/features/live/LiveConnectionWarning.tsx index 54f23413c93..646705d8a34 100644 --- a/public/app/features/live/LiveConnectionWarning.tsx +++ b/public/app/features/live/LiveConnectionWarning.tsx @@ -1,9 +1,10 @@ import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; +import { Unsubscribable } from 'rxjs'; + import { GrafanaTheme2 } from '@grafana/data'; import { config, getGrafanaLiveSrv } from '@grafana/runtime'; import { Alert, stylesFactory } from '@grafana/ui'; -import React, { PureComponent } from 'react'; -import { Unsubscribable } from 'rxjs'; import { contextSrv } from 'app/core/services/context_srv'; export interface Props {} diff --git a/public/app/features/live/centrifuge/LiveDataStream.test.ts b/public/app/features/live/centrifuge/LiveDataStream.test.ts index 200f7475da4..c3311f355f3 100644 --- a/public/app/features/live/centrifuge/LiveDataStream.test.ts +++ b/public/app/features/live/centrifuge/LiveDataStream.test.ts @@ -1,3 +1,7 @@ +import mockConsole, { RestoreConsole } from 'jest-mock-console'; +import { mapValues } from 'lodash'; +import { Observable, Subject, Subscription, Unsubscribable } from 'rxjs'; + import { DataFrameJSON, dataFrameToJSON, @@ -11,13 +15,12 @@ import { LiveChannelScope, LoadingState, } from '@grafana/data'; -import { Observable, Subject, Subscription, Unsubscribable } from 'rxjs'; -import { DataStreamHandlerDeps, LiveDataStream } from './LiveDataStream'; -import { mapValues } from 'lodash'; import { StreamingFrameAction } from '@grafana/runtime'; -import { isStreamingResponseData, StreamingResponseData, StreamingResponseDataType } from '../data/utils'; + import { StreamingDataFrame } from '../data/StreamingDataFrame'; -import mockConsole, { RestoreConsole } from 'jest-mock-console'; +import { isStreamingResponseData, StreamingResponseData, StreamingResponseDataType } from '../data/utils'; + +import { DataStreamHandlerDeps, LiveDataStream } from './LiveDataStream'; type SubjectsInsteadOfObservables = { [key in keyof T]: T[key] extends Observable ? Subject : T[key]; diff --git a/public/app/features/live/centrifuge/LiveDataStream.ts b/public/app/features/live/centrifuge/LiveDataStream.ts index 3f735a52f2c..d992fdeb09b 100644 --- a/public/app/features/live/centrifuge/LiveDataStream.ts +++ b/public/app/features/live/centrifuge/LiveDataStream.ts @@ -1,5 +1,5 @@ -import { LiveDataStreamOptions, StreamingFrameAction, StreamingFrameOptions } from '@grafana/runtime/src/services/live'; -import { toDataQueryError } from '@grafana/runtime/src/utils/toDataQueryError'; +import { map, Observable, ReplaySubject, Subject, Subscriber, Subscription } from 'rxjs'; + import { DataFrameJSON, DataQueryError, @@ -11,11 +11,14 @@ import { LiveChannelId, LoadingState, } from '@grafana/data'; -import { map, Observable, ReplaySubject, Subject, Subscriber, Subscription } from 'rxjs'; -import { DataStreamSubscriptionKey, StreamingDataQueryResponse } from './service'; +import { LiveDataStreamOptions, StreamingFrameAction, StreamingFrameOptions } from '@grafana/runtime/src/services/live'; +import { toDataQueryError } from '@grafana/runtime/src/utils/toDataQueryError'; + import { getStreamingFrameOptions, StreamingDataFrame } from '../data/StreamingDataFrame'; import { StreamingResponseDataType } from '../data/utils'; +import { DataStreamSubscriptionKey, StreamingDataQueryResponse } from './service'; + const bufferIfNot = (canEmitObservable: Observable) => (source: Observable): Observable => { diff --git a/public/app/features/live/centrifuge/channel.ts b/public/app/features/live/centrifuge/channel.ts index 0ad529a6598..c15c73572b2 100644 --- a/public/app/features/live/centrifuge/channel.ts +++ b/public/app/features/live/centrifuge/channel.ts @@ -1,3 +1,13 @@ +import Centrifuge, { + JoinLeaveContext, + PublicationContext, + SubscribeErrorContext, + SubscribeSuccessContext, + SubscriptionEvents, + UnsubscribeContext, +} from 'centrifuge/dist/centrifuge'; +import { Subject, of, Observable } from 'rxjs'; + import { LiveChannelStatusEvent, LiveChannelEvent, @@ -8,16 +18,6 @@ import { DataFrameJSON, isValidLiveChannelAddress, } from '@grafana/data'; -import Centrifuge, { - JoinLeaveContext, - PublicationContext, - SubscribeErrorContext, - SubscribeSuccessContext, - SubscriptionEvents, - UnsubscribeContext, -} from 'centrifuge/dist/centrifuge'; - -import { Subject, of, Observable } from 'rxjs'; /** * Internal class that maps Centrifuge support to GrafanaLive diff --git a/public/app/features/live/centrifuge/service.ts b/public/app/features/live/centrifuge/service.ts index d65154762e7..3cb95ff6ca6 100644 --- a/public/app/features/live/centrifuge/service.ts +++ b/public/app/features/live/centrifuge/service.ts @@ -1,12 +1,6 @@ import Centrifuge from 'centrifuge/dist/centrifuge'; -import { - GrafanaLiveSrv, - LiveDataStreamOptions, - LiveQueryDataOptions, - StreamingFrameAction, - StreamingFrameOptions, -} from '@grafana/runtime/src/services/live'; import { BehaviorSubject, Observable, share, startWith } from 'rxjs'; + import { DataQueryError, DataQueryResponse, @@ -15,11 +9,20 @@ import { LiveChannelId, toLiveChannelId, } from '@grafana/data'; -import { CentrifugeLiveChannel } from './channel'; -import { LiveDataStream } from './LiveDataStream'; -import { StreamingResponseData } from '../data/utils'; -import { BackendDataSourceResponse } from '@grafana/runtime/src/utils/queryResponse'; import { FetchResponse } from '@grafana/runtime/src/services/backendSrv'; +import { + GrafanaLiveSrv, + LiveDataStreamOptions, + LiveQueryDataOptions, + StreamingFrameAction, + StreamingFrameOptions, +} from '@grafana/runtime/src/services/live'; +import { BackendDataSourceResponse } from '@grafana/runtime/src/utils/queryResponse'; + +import { StreamingResponseData } from '../data/utils'; + +import { LiveDataStream } from './LiveDataStream'; +import { CentrifugeLiveChannel } from './channel'; export type CentrifugeSrvDeps = { appUrl: string; diff --git a/public/app/features/live/centrifuge/service.worker.ts b/public/app/features/live/centrifuge/service.worker.ts index e47681175f3..a168bc34824 100644 --- a/public/app/features/live/centrifuge/service.worker.ts +++ b/public/app/features/live/centrifuge/service.worker.ts @@ -1,10 +1,13 @@ -import { CentrifugeService, CentrifugeSrvDeps } from './service'; -import * as comlink from 'comlink'; import './transferHandlers'; -import { remoteObservableAsObservable } from './remoteObservable'; + +import * as comlink from 'comlink'; + import { LiveChannelAddress } from '@grafana/data'; import { LiveDataStreamOptions, LiveQueryDataOptions } from '@grafana/runtime'; +import { remoteObservableAsObservable } from './remoteObservable'; +import { CentrifugeService, CentrifugeSrvDeps } from './service'; + let centrifuge: CentrifugeService; const initialize = ( diff --git a/public/app/features/live/centrifuge/serviceWorkerProxy.ts b/public/app/features/live/centrifuge/serviceWorkerProxy.ts index c53d2e49507..9cbea1b8312 100644 --- a/public/app/features/live/centrifuge/serviceWorkerProxy.ts +++ b/public/app/features/live/centrifuge/serviceWorkerProxy.ts @@ -1,12 +1,14 @@ -import { CentrifugeSrv, CentrifugeSrvDeps } from './service'; -import { RemoteCentrifugeService } from './service.worker'; import './transferHandlers'; import * as comlink from 'comlink'; import { asyncScheduler, Observable, observeOn } from 'rxjs'; + import { LiveChannelAddress, LiveChannelEvent } from '@grafana/data'; -import { promiseWithRemoteObservableAsObservable } from './remoteObservable'; + import { createWorker } from './createCentrifugeServiceWorker'; +import { promiseWithRemoteObservableAsObservable } from './remoteObservable'; +import { CentrifugeSrv, CentrifugeSrvDeps } from './service'; +import { RemoteCentrifugeService } from './service.worker'; export class CentrifugeServiceWorkerProxy implements CentrifugeSrv { private centrifugeWorker; diff --git a/public/app/features/live/dashboard/DashboardChangedModal.tsx b/public/app/features/live/dashboard/DashboardChangedModal.tsx index 26e00e17930..b8db7b2f8c9 100644 --- a/public/app/features/live/dashboard/DashboardChangedModal.tsx +++ b/public/app/features/live/dashboard/DashboardChangedModal.tsx @@ -1,10 +1,12 @@ -import React, { PureComponent } from 'react'; -import { Modal, stylesFactory } from '@grafana/ui'; import { css } from '@emotion/css'; -import { dashboardWatcher } from './dashboardWatcher'; -import { config } from '@grafana/runtime'; -import { DashboardEvent, DashboardEventAction } from './types'; +import React, { PureComponent } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Modal, stylesFactory } from '@grafana/ui'; + +import { dashboardWatcher } from './dashboardWatcher'; +import { DashboardEvent, DashboardEventAction } from './types'; interface Props { event?: DashboardEvent; diff --git a/public/app/features/live/dashboard/dashboardWatcher.ts b/public/app/features/live/dashboard/dashboardWatcher.ts index f4f89713845..e7cccd2b721 100644 --- a/public/app/features/live/dashboard/dashboardWatcher.ts +++ b/public/app/features/live/dashboard/dashboardWatcher.ts @@ -1,6 +1,5 @@ -import { getGrafanaLiveSrv, locationService } from '@grafana/runtime'; -import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; -import { appEvents, contextSrv } from 'app/core/core'; +import { Unsubscribable } from 'rxjs'; + import { AppEvents, isLiveChannelMessageEvent, @@ -10,11 +9,15 @@ import { LiveChannelEvent, LiveChannelScope, } from '@grafana/data'; +import { getGrafanaLiveSrv, locationService } from '@grafana/runtime'; +import { appEvents, contextSrv } from 'app/core/core'; +import { sessionId } from 'app/features/live'; + +import { ShowModalReactEvent } from '../../../types/events'; +import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; + import { DashboardChangedModal } from './DashboardChangedModal'; import { DashboardEvent, DashboardEventAction } from './types'; -import { sessionId } from 'app/features/live'; -import { ShowModalReactEvent } from '../../../types/events'; -import { Unsubscribable } from 'rxjs'; class DashboardWatcher { channel?: LiveChannelAddress; // path to the channel diff --git a/public/app/features/live/index.ts b/public/app/features/live/index.ts index 31a2001c828..b1ae4baca8f 100644 --- a/public/app/features/live/index.ts +++ b/public/app/features/live/index.ts @@ -1,10 +1,12 @@ import { config, getBackendSrv, getGrafanaLiveSrv, setGrafanaLiveSrv } from '@grafana/runtime'; -import { GrafanaLiveService } from './live'; -import { contextSrv } from '../../core/services/context_srv'; -import { CentrifugeServiceWorkerProxy } from './centrifuge/serviceWorkerProxy'; -import { CentrifugeService } from './centrifuge/service'; import { liveTimer } from 'app/features/dashboard/dashgrid/liveTimer'; +import { contextSrv } from '../../core/services/context_srv'; + +import { CentrifugeService } from './centrifuge/service'; +import { CentrifugeServiceWorkerProxy } from './centrifuge/serviceWorkerProxy'; +import { GrafanaLiveService } from './live'; + export const sessionId = (window as any)?.grafanaBootData?.user?.id + '/' + diff --git a/public/app/features/live/live.test.ts b/public/app/features/live/live.test.ts index 58355588a39..e31feeb9715 100644 --- a/public/app/features/live/live.test.ts +++ b/public/app/features/live/live.test.ts @@ -1,10 +1,12 @@ -import { GrafanaLiveService } from './live'; -import { StreamingDataQueryResponse } from './centrifuge/service'; +import mockConsole, { RestoreConsole } from 'jest-mock-console'; import { Subject } from 'rxjs'; + import { DataQueryResponse, FieldType, LiveChannelScope } from '@grafana/data'; + +import { StreamingDataQueryResponse } from './centrifuge/service'; import { StreamingDataFrame } from './data/StreamingDataFrame'; import { StreamingResponseDataType } from './data/utils'; -import mockConsole, { RestoreConsole } from 'jest-mock-console'; +import { GrafanaLiveService } from './live'; describe('GrafanaLiveService', () => { let restoreConsole: RestoreConsole | undefined; diff --git a/public/app/features/live/live.ts b/public/app/features/live/live.ts index d541a282500..689e5fcf4a4 100644 --- a/public/app/features/live/live.ts +++ b/public/app/features/live/live.ts @@ -1,15 +1,16 @@ -import { BackendSrv, GrafanaLiveSrv, toDataQueryResponse } from '@grafana/runtime'; -import { CentrifugeSrv, StreamingDataQueryResponse } from './centrifuge/service'; +import { from, map, of, switchMap } from 'rxjs'; import { DataFrame, toLiveChannelId } from '@grafana/data'; -import { StreamingDataFrame } from './data/StreamingDataFrame'; -import { isStreamingResponseData, StreamingResponseDataType } from './data/utils'; -import { from, map, of, switchMap } from 'rxjs'; +import { BackendSrv, GrafanaLiveSrv, toDataQueryResponse } from '@grafana/runtime'; import { standardStreamOptionsProvider, toStreamingDataResponse, } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import { CentrifugeSrv, StreamingDataQueryResponse } from './centrifuge/service'; +import { StreamingDataFrame } from './data/StreamingDataFrame'; +import { isStreamingResponseData, StreamingResponseDataType } from './data/utils'; + type GrafanaLiveServiceDeps = { centrifugeSrv: CentrifugeSrv; backendSrv: BackendSrv; diff --git a/public/app/features/live/pages/AddNewRule.tsx b/public/app/features/live/pages/AddNewRule.tsx index 5fa392361d0..fea6a9fa681 100644 --- a/public/app/features/live/pages/AddNewRule.tsx +++ b/public/app/features/live/pages/AddNewRule.tsx @@ -1,8 +1,10 @@ import React, { useState } from 'react'; -import { Input, Field, Button, ValuePicker, HorizontalGroup } from '@grafana/ui'; -import { DataSourcePicker, getBackendSrv } from '@grafana/runtime'; + import { DataSourceRef, LiveChannelScope, SelectableValue } from '@grafana/data'; +import { DataSourcePicker, getBackendSrv } from '@grafana/runtime'; +import { Input, Field, Button, ValuePicker, HorizontalGroup } from '@grafana/ui'; import { useAppNotification } from 'app/core/copy/appNotification'; + import { Rule } from './types'; interface Props { diff --git a/public/app/features/live/pages/CloudAdminPage.tsx b/public/app/features/live/pages/CloudAdminPage.tsx index b24dba5e6c4..fe3face9db2 100644 --- a/public/app/features/live/pages/CloudAdminPage.tsx +++ b/public/app/features/live/pages/CloudAdminPage.tsx @@ -1,10 +1,12 @@ +import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { useStyles } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; -import { css } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; + import { GrafanaCloudBackend } from './types'; export default function CloudAdminPage() { diff --git a/public/app/features/live/pages/FeatureTogglePage.tsx b/public/app/features/live/pages/FeatureTogglePage.tsx index 4ccdaef6f2e..a1d3e635c5a 100644 --- a/public/app/features/live/pages/FeatureTogglePage.tsx +++ b/public/app/features/live/pages/FeatureTogglePage.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import Page from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; diff --git a/public/app/features/live/pages/LiveStatusPage.tsx b/public/app/features/live/pages/LiveStatusPage.tsx index b5f2231db53..569853591af 100644 --- a/public/app/features/live/pages/LiveStatusPage.tsx +++ b/public/app/features/live/pages/LiveStatusPage.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import Page from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; diff --git a/public/app/features/live/pages/PipelineAdminPage.tsx b/public/app/features/live/pages/PipelineAdminPage.tsx index 2edda347c00..2face908dc6 100644 --- a/public/app/features/live/pages/PipelineAdminPage.tsx +++ b/public/app/features/live/pages/PipelineAdminPage.tsx @@ -1,11 +1,13 @@ import React, { useEffect, useState, ChangeEvent } from 'react'; + import { getBackendSrv } from '@grafana/runtime'; import { Input } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; -import { Rule } from './types'; -import { PipelineTable } from './PipelineTable'; + import { AddNewRule } from './AddNewRule'; +import { PipelineTable } from './PipelineTable'; +import { Rule } from './types'; export default function PipelineAdminPage() { const [rules, setRules] = useState([]); diff --git a/public/app/features/live/pages/PipelineTable.tsx b/public/app/features/live/pages/PipelineTable.tsx index 32873a40c16..073417618af 100644 --- a/public/app/features/live/pages/PipelineTable.tsx +++ b/public/app/features/live/pages/PipelineTable.tsx @@ -1,12 +1,14 @@ +import { css } from '@emotion/css'; import React, { useEffect, useState } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { Tag, useStyles, IconButton } from '@grafana/ui'; -import { css } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; -import { Rule, Output, RuleType } from './types'; -import { RuleModal } from './RuleModal'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { RuleModal } from './RuleModal'; +import { Rule, Output, RuleType } from './types'; + function renderOutputTags(key: string, output?: Output): React.ReactNode { if (!output?.type) { return null; diff --git a/public/app/features/live/pages/RuleModal.tsx b/public/app/features/live/pages/RuleModal.tsx index 0f2686024cb..95b4a8d4f2c 100644 --- a/public/app/features/live/pages/RuleModal.tsx +++ b/public/app/features/live/pages/RuleModal.tsx @@ -1,13 +1,15 @@ -import React, { useState, useMemo } from 'react'; -import { Modal, TabContent, TabsBar, Tab, Button, useStyles } from '@grafana/ui'; -import { Rule, RuleType, PipeLineEntitiesInfo, RuleSetting } from './types'; -import { getBackendSrv } from '@grafana/runtime'; import { css } from '@emotion/css'; +import React, { useState, useMemo } from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { RuleSettingsEditor } from './RuleSettingsEditor'; -import { getPipeLineEntities } from './utils'; +import { getBackendSrv } from '@grafana/runtime'; +import { Modal, TabContent, TabsBar, Tab, Button, useStyles } from '@grafana/ui'; + import { RuleSettingsArray } from './RuleSettingsArray'; +import { RuleSettingsEditor } from './RuleSettingsEditor'; import { RuleTest } from './RuleTest'; +import { Rule, RuleType, PipeLineEntitiesInfo, RuleSetting } from './types'; +import { getPipeLineEntities } from './utils'; interface Props { rule: Rule; diff --git a/public/app/features/live/pages/RuleSettingsArray.tsx b/public/app/features/live/pages/RuleSettingsArray.tsx index c209e9d578d..fe9ed83163c 100644 --- a/public/app/features/live/pages/RuleSettingsArray.tsx +++ b/public/app/features/live/pages/RuleSettingsArray.tsx @@ -1,8 +1,11 @@ import React, { useState } from 'react'; + +import { Select } from '@grafana/ui'; + +import { SelectableValue } from '../../../../../packages/grafana-data/src'; + import { RuleSettingsEditor } from './RuleSettingsEditor'; import { RuleType, RuleSetting, PipeLineEntitiesInfo } from './types'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '../../../../../packages/grafana-data/src'; interface Props { ruleType: RuleType; diff --git a/public/app/features/live/pages/RuleSettingsEditor.tsx b/public/app/features/live/pages/RuleSettingsEditor.tsx index e7c7411127b..9a34068e8fc 100644 --- a/public/app/features/live/pages/RuleSettingsEditor.tsx +++ b/public/app/features/live/pages/RuleSettingsEditor.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { CodeEditor, Select } from '@grafana/ui'; + import { RuleType, RuleSetting, PipeLineEntitiesInfo } from './types'; interface Props { diff --git a/public/app/features/live/pages/RuleTest.tsx b/public/app/features/live/pages/RuleTest.tsx index d6494ccf82e..70e8622efa5 100644 --- a/public/app/features/live/pages/RuleTest.tsx +++ b/public/app/features/live/pages/RuleTest.tsx @@ -1,9 +1,11 @@ -import React, { useState } from 'react'; -import { Button, CodeEditor, Table, useStyles, Field } from '@grafana/ui'; -import { ChannelFrame, Rule } from './types'; -import { getBackendSrv, config } from '@grafana/runtime'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; + import { dataFrameFromJSON, getDisplayProcessor, GrafanaTheme } from '@grafana/data'; +import { getBackendSrv, config } from '@grafana/runtime'; +import { Button, CodeEditor, Table, useStyles, Field } from '@grafana/ui'; + +import { ChannelFrame, Rule } from './types'; interface Props { rule: Rule; diff --git a/public/app/features/live/pages/utils.ts b/public/app/features/live/pages/utils.ts index a2c3dc2757c..db986d17c27 100644 --- a/public/app/features/live/pages/utils.ts +++ b/public/app/features/live/pages/utils.ts @@ -1,4 +1,5 @@ import { getBackendSrv } from '@grafana/runtime'; + import { PipelineListOption, PipeLineEntitiesInfo } from './types'; export async function getPipeLineEntities(): Promise { diff --git a/public/app/features/manage-dashboards/DashboardImportPage.tsx b/public/app/features/manage-dashboards/DashboardImportPage.tsx index 378b9fe4cc5..9ea96938469 100644 --- a/public/app/features/manage-dashboards/DashboardImportPage.tsx +++ b/public/app/features/manage-dashboards/DashboardImportPage.tsx @@ -1,6 +1,7 @@ +import { css } from '@emotion/css'; import React, { FormEvent, PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { css } from '@emotion/css'; + import { AppEvents, GrafanaTheme2, LoadingState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; @@ -18,16 +19,18 @@ import { VerticalGroup, withTheme2, } from '@grafana/ui'; -import Page from 'app/core/components/Page/Page'; -import { ImportDashboardOverview } from './components/ImportDashboardOverview'; -import { validateDashboardJson, validateGcomDashboard } from './utils/validation'; -import { fetchGcomDashboard, importDashboardJson } from './state/actions'; import appEvents from 'app/core/app_events'; +import Page from 'app/core/components/Page/Page'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; + import { cleanUpAction } from '../../core/actions/cleanUp'; +import { ImportDashboardOverview } from './components/ImportDashboardOverview'; +import { fetchGcomDashboard, importDashboardJson } from './state/actions'; +import { validateDashboardJson, validateGcomDashboard } from './utils/validation'; + type DashboardImportPageRouteSearchParams = { gcomDashboardId?: string; }; diff --git a/public/app/features/manage-dashboards/SnapshotListPage.tsx b/public/app/features/manage-dashboards/SnapshotListPage.tsx index 7755b38989f..5aa91777a34 100644 --- a/public/app/features/manage-dashboards/SnapshotListPage.tsx +++ b/public/app/features/manage-dashboards/SnapshotListPage.tsx @@ -1,11 +1,14 @@ import React, { FC } from 'react'; import { MapStateToProps, connect } from 'react-redux'; + import { NavModel } from '@grafana/data'; import Page from 'app/core/components/Page/Page'; -import { StoreState } from 'app/types'; -import { SnapshotListTable } from './components/SnapshotListTable'; -import { GrafanaRouteComponentProps } from '../../core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; + +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; + +import { SnapshotListTable } from './components/SnapshotListTable'; interface ConnectedProps { navModel: NavModel; diff --git a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx index 332b8ff1c13..ea0f1cc26b6 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardForm.tsx @@ -1,4 +1,8 @@ import React, { FC, useEffect, useState } from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; +import { DataSourcePicker } from '@grafana/runtime'; +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { Button, Field, @@ -10,11 +14,8 @@ import { InputControl, Legend, } from '@grafana/ui'; -import { DataSourcePicker } from '@grafana/runtime'; -import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; -import { selectors } from '@grafana/e2e-selectors'; - import { FolderPicker } from 'app/core/components/Select/FolderPicker'; + import { DashboardInput, DashboardInputs, @@ -23,6 +24,7 @@ import { LibraryPanelInputState, } from '../state/reducers'; import { validateTitle, validateUid } from '../utils/validation'; + import { ImportDashboardLibraryPanelsList } from './ImportDashboardLibraryPanelsList'; interface Props extends Pick, 'register' | 'errors' | 'control' | 'getValues' | 'watch'> { diff --git a/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx b/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx index e6a3b677030..cb3b47b7b71 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx @@ -1,10 +1,11 @@ +import { css } from '@emotion/css'; import React, { ReactElement } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Field, useStyles2 } from '@grafana/ui'; -import { LibraryPanelInput, LibraryPanelInputState } from '../state/reducers'; -import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; import { LibraryPanelCard } from '../../library-panels/components/LibraryPanelCard/LibraryPanelCard'; +import { LibraryPanelInput, LibraryPanelInputState } from '../state/reducers'; interface ImportDashboardLibraryPanelsListProps { inputs: LibraryPanelInput[]; diff --git a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx index 275a12d18ac..9fdbbedb5fe 100644 --- a/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx +++ b/public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx @@ -1,12 +1,15 @@ import React, { PureComponent } from 'react'; -import { dateTimeFormat } from '@grafana/data'; -import { Form, Legend } from '@grafana/ui'; import { connect, ConnectedProps } from 'react-redux'; -import { ImportDashboardForm } from './ImportDashboardForm'; + +import { dateTimeFormat } from '@grafana/data'; +import { locationService, reportInteraction } from '@grafana/runtime'; +import { Form, Legend } from '@grafana/ui'; +import { StoreState } from 'app/types'; + import { clearLoadedDashboard, importDashboard } from '../state/actions'; import { DashboardSource, ImportDashboardDTO } from '../state/reducers'; -import { StoreState } from 'app/types'; -import { locationService, reportInteraction } from '@grafana/runtime'; + +import { ImportDashboardForm } from './ImportDashboardForm'; const IMPORT_FINISHED_EVENT_NAME = 'dashboard_import_imported'; diff --git a/public/app/features/manage-dashboards/components/SnapshotListTable.test.tsx b/public/app/features/manage-dashboards/components/SnapshotListTable.test.tsx index 3ecdb01fb89..ac89f7f9684 100644 --- a/public/app/features/manage-dashboards/components/SnapshotListTable.test.tsx +++ b/public/app/features/manage-dashboards/components/SnapshotListTable.test.tsx @@ -1,4 +1,5 @@ import { locationService } from '@grafana/runtime'; + import { getSnapshots } from './SnapshotListTable'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx index 29c7c38325e..8eccb0b1513 100644 --- a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx +++ b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx @@ -1,9 +1,11 @@ import React, { FC, useState, useCallback } from 'react'; -import { ConfirmModal, Button, LinkButton } from '@grafana/ui'; -import { getBackendSrv, locationService } from '@grafana/runtime'; -import { Snapshot } from '../types'; import useAsync from 'react-use/lib/useAsync'; +import { getBackendSrv, locationService } from '@grafana/runtime'; +import { ConfirmModal, Button, LinkButton } from '@grafana/ui'; + +import { Snapshot } from '../types'; + export function getSnapshots() { return getBackendSrv() .get('/api/dashboard/snapshots') diff --git a/public/app/features/manage-dashboards/state/actions.test.ts b/public/app/features/manage-dashboards/state/actions.test.ts index 7c55fb72f20..d6c366ed545 100644 --- a/public/app/features/manage-dashboards/state/actions.test.ts +++ b/public/app/features/manage-dashboards/state/actions.test.ts @@ -1,5 +1,7 @@ -import { setBackendSrv } from '@grafana/runtime'; import { thunkTester } from 'test/core/thunk/thunkTester'; + +import { setBackendSrv } from '@grafana/runtime'; + import { importDashboard } from './actions'; import { DataSourceInput, ImportDashboardDTO, initialImportDashboardState, InputType } from './reducers'; diff --git a/public/app/features/manage-dashboards/state/actions.ts b/public/app/features/manage-dashboards/state/actions.ts index 99499d4960c..711e8f60e5a 100644 --- a/public/app/features/manage-dashboards/state/actions.ts +++ b/public/app/features/manage-dashboards/state/actions.ts @@ -1,4 +1,15 @@ import { DataSourceInstanceSettings, locationUtil } from '@grafana/data'; +import { getDataSourceSrv, locationService, getBackendSrv } from '@grafana/runtime'; +import { notifyApp } from 'app/core/actions'; +import { createErrorNotification } from 'app/core/copy/appNotification'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { DashboardDataDTO, DashboardDTO, FolderInfo, PermissionLevelString, ThunkResult } from 'app/types'; + +import { LibraryElementExport } from '../../dashboard/components/DashExportModal/DashboardExporter'; +import { getLibraryPanel } from '../../library-panels/state/api'; +import { LibraryElementDTO, LibraryElementKind } from '../../library-panels/types'; +import { DashboardSearchHit } from '../../search/types'; + import { clearDashboard, fetchDashboard, @@ -12,15 +23,6 @@ import { setJsonDashboard, setLibraryPanelInputs, } from './reducers'; -import { DashboardDataDTO, DashboardDTO, FolderInfo, PermissionLevelString, ThunkResult } from 'app/types'; -import { createErrorNotification } from 'app/core/copy/appNotification'; -import { notifyApp } from 'app/core/actions'; -import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; -import { getDataSourceSrv, locationService, getBackendSrv } from '@grafana/runtime'; -import { DashboardSearchHit } from '../../search/types'; -import { getLibraryPanel } from '../../library-panels/state/api'; -import { LibraryElementDTO, LibraryElementKind } from '../../library-panels/types'; -import { LibraryElementExport } from '../../dashboard/components/DashExportModal/DashboardExporter'; export function fetchGcomDashboard(id: string): ThunkResult { return async (dispatch) => { diff --git a/public/app/features/manage-dashboards/state/reducers.test.ts b/public/app/features/manage-dashboards/state/reducers.test.ts index 8d845d271f6..5b25d1a394e 100644 --- a/public/app/features/manage-dashboards/state/reducers.test.ts +++ b/public/app/features/manage-dashboards/state/reducers.test.ts @@ -1,4 +1,8 @@ +import { LoadingState } from '@grafana/data'; + import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { LibraryElementDTO } from '../../library-panels/types'; + import { clearDashboard, DashboardSource, @@ -14,8 +18,6 @@ import { setJsonDashboard, setLibraryPanelInputs, } from './reducers'; -import { LibraryElementDTO } from '../../library-panels/types'; -import { LoadingState } from '@grafana/data'; describe('importDashboardReducer', () => { describe('when setGcomDashboard action is dispatched', () => { diff --git a/public/app/features/manage-dashboards/state/reducers.ts b/public/app/features/manage-dashboards/state/reducers.ts index 89940277dd1..b2127692d74 100644 --- a/public/app/features/manage-dashboards/state/reducers.ts +++ b/public/app/features/manage-dashboards/state/reducers.ts @@ -1,5 +1,7 @@ import { createSlice, Draft, PayloadAction } from '@reduxjs/toolkit'; + import { DataSourceInstanceSettings, LoadingState } from '@grafana/data'; + import { LibraryElementDTO } from '../../library-panels/types'; export enum DashboardSource { diff --git a/public/app/features/manage-dashboards/utils/validation.ts b/public/app/features/manage-dashboards/utils/validation.ts index f68d3c30e37..bee8a2200c8 100644 --- a/public/app/features/manage-dashboards/utils/validation.ts +++ b/public/app/features/manage-dashboards/utils/validation.ts @@ -1,6 +1,7 @@ -import { validationSrv } from '../services/ValidationSrv'; import { getBackendSrv } from '@grafana/runtime'; +import { validationSrv } from '../services/ValidationSrv'; + export const validateDashboardJson = (json: string) => { try { JSON.parse(json); diff --git a/public/app/features/notifications/NotificationsPage.tsx b/public/app/features/notifications/NotificationsPage.tsx new file mode 100644 index 00000000000..e809b8e49fa --- /dev/null +++ b/public/app/features/notifications/NotificationsPage.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { connect, ConnectedProps } from 'react-redux'; + +import { config } from '@grafana/runtime'; + +import Page from '../../core/components/Page/Page'; +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; +import { getNavModel } from '../../core/selectors/navModel'; +import { StoreState } from '../../types'; + +import { StoredNotifications } from './StoredNotifications'; + +const mapStateToProps = (state: StoreState) => ({ + navModel: getNavModel(state.navIndex, 'notifications'), +}); + +const connector = connect(mapStateToProps, undefined); + +interface OwnProps extends GrafanaRouteComponentProps {} + +type Props = OwnProps & ConnectedProps; + +export const NotificationsPage = ({ navModel }: Props) => { + if (!config.featureToggles.persistNotifications) { + return null; + } + + return ( + + + + + + ); +}; + +export default connect(mapStateToProps)(NotificationsPage); diff --git a/public/app/features/notifications/StoredNotifications.tsx b/public/app/features/notifications/StoredNotifications.tsx new file mode 100644 index 00000000000..cd126c4d030 --- /dev/null +++ b/public/app/features/notifications/StoredNotifications.tsx @@ -0,0 +1,124 @@ +import { css, cx } from '@emotion/css'; +import React, { useRef } from 'react'; +import { useEffectOnce } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, Icon, useStyles2 } from '@grafana/ui'; +import { StoredNotificationItem } from 'app/core/components/AppNotifications/StoredNotificationItem'; +import { + clearAllNotifications, + clearNotification, + readAllNotifications, + selectWarningsAndErrors, + selectLastReadTimestamp, +} from 'app/core/reducers/appNotification'; +import { useDispatch, useSelector } from 'app/types'; + +export function StoredNotifications() { + const dispatch = useDispatch(); + const notifications = useSelector((state) => selectWarningsAndErrors(state.appNotifications)); + const lastReadTimestamp = useRef(useSelector((state) => selectLastReadTimestamp(state.appNotifications))); + const styles = useStyles2(getStyles); + + useEffectOnce(() => { + dispatch(readAllNotifications(Date.now())); + }); + + const onClearNotification = (id: string) => { + dispatch(clearNotification(id)); + }; + + const clearAllNotifs = () => { + dispatch(clearAllNotifications()); + }; + + if (notifications.length === 0) { + return ( +
+ + Notifications you have received will appear here. +
+ ); + } + + return ( +
+ +
    + {notifications.map((notif) => ( +
  • lastReadTimestamp.current })} + > + onClearNotification(notif.id)} + timestamp={notif.timestamp} + traceId={notif.traceId} + > + {notif.text} + +
  • + ))} +
+
+ ); +} + +function getStyles(theme: GrafanaTheme2) { + return { + smallText: css({ + fontSize: theme.typography.pxToRem(10), + color: theme.colors.text.secondary, + }), + side: css({ + display: 'flex', + flexDirection: 'column', + padding: '3px 6px', + paddingTop: theme.spacing(1), + alignItems: 'flex-end', + justifyContent: 'space-between', + flexShrink: 0, + }), + list: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + }), + listItem: css({ + listStyle: 'none', + gap: theme.spacing(1), + alignItems: 'center', + position: 'relative', + }), + newItem: css({ + '&::before': { + content: '""', + height: '100%', + position: 'absolute', + left: '-7px', + top: 0, + background: theme.colors.gradients.brandVertical, + width: theme.spacing(0.5), + borderRadius: theme.shape.borderRadius(1), + }, + }), + noNotifsWrapper: css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: theme.spacing(1), + }), + wrapper: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + }), + clearAll: css({ + alignSelf: 'flex-end', + }), + }; +} diff --git a/public/app/features/org/NewOrgPage.tsx b/public/app/features/org/NewOrgPage.tsx index c9e940e2b25..34d2619110b 100644 --- a/public/app/features/org/NewOrgPage.tsx +++ b/public/app/features/org/NewOrgPage.tsx @@ -1,11 +1,14 @@ import React, { FC } from 'react'; -import Page from 'app/core/components/Page/Page'; -import { Button, Input, Field, Form } from '@grafana/ui'; -import { StoreState } from 'app/types'; import { connect, ConnectedProps } from 'react-redux'; -import { getNavModel } from '../../core/selectors/navModel'; -import { createOrganization } from './state/actions'; + +import { Button, Input, Field, Form } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; import { getConfig } from 'app/core/config'; +import { StoreState } from 'app/types'; + +import { getNavModel } from '../../core/selectors/navModel'; + +import { createOrganization } from './state/actions'; const mapStateToProps = (state: StoreState) => { return { navModel: getNavModel(state.navIndex, 'global-orgs') }; diff --git a/public/app/features/org/OrgDetailsPage.test.tsx b/public/app/features/org/OrgDetailsPage.test.tsx index 93cca7cc902..7d927f2f00b 100644 --- a/public/app/features/org/OrgDetailsPage.test.tsx +++ b/public/app/features/org/OrgDetailsPage.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; +import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + import { NavModel } from '@grafana/data'; -import { OrgDetailsPage, Props } from './OrgDetailsPage'; import { Organization } from '../../types'; -import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + +import { OrgDetailsPage, Props } from './OrgDetailsPage'; import { setOrganizationName } from './state/reducers'; jest.mock('app/core/core', () => { diff --git a/public/app/features/org/OrgDetailsPage.tsx b/public/app/features/org/OrgDetailsPage.tsx index 75776303601..5059ad5dd25 100644 --- a/public/app/features/org/OrgDetailsPage.tsx +++ b/public/app/features/org/OrgDetailsPage.tsx @@ -1,16 +1,17 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; -import { NavModel } from '@grafana/data'; -import Page from 'app/core/components/Page/Page'; -import OrgProfile from './OrgProfile'; -import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences'; -import { loadOrganization, updateOrganization } from './state/actions'; -import { AccessControlAction, Organization, StoreState } from 'app/types'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { setOrganizationName } from './state/reducers'; +import { NavModel } from '@grafana/data'; import { VerticalGroup } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; +import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences'; import { contextSrv } from 'app/core/core'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { AccessControlAction, Organization, StoreState } from 'app/types'; + +import OrgProfile from './OrgProfile'; +import { loadOrganization, updateOrganization } from './state/actions'; +import { setOrganizationName } from './state/reducers'; export interface Props { navModel: NavModel; diff --git a/public/app/features/org/OrgProfile.test.tsx b/public/app/features/org/OrgProfile.test.tsx index 9eceebcee95..459ad6cbe6a 100644 --- a/public/app/features/org/OrgProfile.test.tsx +++ b/public/app/features/org/OrgProfile.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import OrgProfile, { Props } from './OrgProfile'; jest.mock('app/core/core', () => { diff --git a/public/app/features/org/OrgProfile.tsx b/public/app/features/org/OrgProfile.tsx index f4b9a0bd293..42e2abffe9b 100644 --- a/public/app/features/org/OrgProfile.tsx +++ b/public/app/features/org/OrgProfile.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { Input, Field, FieldSet, Button, Form } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/org/SelectOrgPage.tsx b/public/app/features/org/SelectOrgPage.tsx index 1367e5d6573..273f699cc65 100644 --- a/public/app/features/org/SelectOrgPage.tsx +++ b/public/app/features/org/SelectOrgPage.tsx @@ -1,11 +1,13 @@ import React, { FC } from 'react'; -import Page from 'app/core/components/Page/Page'; -import { config } from '@grafana/runtime'; -import { StoreState, UserOrg } from 'app/types'; -import { useEffectOnce } from 'react-use'; -import { Button, HorizontalGroup } from '@grafana/ui'; -import { getUserOrganizations, setUserOrganization } from './state/actions'; import { connect, ConnectedProps } from 'react-redux'; +import { useEffectOnce } from 'react-use'; + +import { config } from '@grafana/runtime'; +import { Button, HorizontalGroup } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; +import { StoreState, UserOrg } from 'app/types'; + +import { getUserOrganizations, setUserOrganization } from './state/actions'; const navModel = { main: { diff --git a/public/app/features/org/UserInviteForm.tsx b/public/app/features/org/UserInviteForm.tsx index 3ab5073d6c9..e94c6c9e9cf 100644 --- a/public/app/features/org/UserInviteForm.tsx +++ b/public/app/features/org/UserInviteForm.tsx @@ -1,4 +1,7 @@ import React from 'react'; + +import { locationUtil } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; import { HorizontalGroup, Button, @@ -10,10 +13,9 @@ import { Field, InputControl, } from '@grafana/ui'; -import { locationService } from '@grafana/runtime'; -import { locationUtil } from '@grafana/data'; import { getConfig } from 'app/core/config'; import { OrgRole, useDispatch } from 'app/types'; + import { addInvitee } from '../invites/state/actions'; const roles = [ diff --git a/public/app/features/org/UserInvitePage.tsx b/public/app/features/org/UserInvitePage.tsx index affe62a13e9..8afa065a0b7 100644 --- a/public/app/features/org/UserInvitePage.tsx +++ b/public/app/features/org/UserInvitePage.tsx @@ -1,11 +1,13 @@ import React, { FC } from 'react'; import { connect } from 'react-redux'; -import UserInviteForm from './UserInviteForm'; + +import { NavModel } from '@grafana/data'; +import Page from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types/store'; -import Page from 'app/core/components/Page/Page'; -import { NavModel } from '@grafana/data'; + +import UserInviteForm from './UserInviteForm'; interface Props { navModel: NavModel; diff --git a/public/app/features/org/state/actions.test.ts b/public/app/features/org/state/actions.test.ts index 9dd97426e5f..924d2aef871 100644 --- a/public/app/features/org/state/actions.test.ts +++ b/public/app/features/org/state/actions.test.ts @@ -1,8 +1,10 @@ -import { updateOrganization, setUserOrganization, getUserOrganizations } from './actions'; -import { updateConfigurationSubtitle } from 'app/core/actions'; import { thunkTester } from 'test/core/thunk/thunkTester'; + +import { updateConfigurationSubtitle } from 'app/core/actions'; import { OrgRole } from 'app/types'; +import { updateOrganization, setUserOrganization, getUserOrganizations } from './actions'; + const setup = () => { const initialState = { organization: { diff --git a/public/app/features/org/state/actions.ts b/public/app/features/org/state/actions.ts index 6b01d1b2f91..7b382a71751 100644 --- a/public/app/features/org/state/actions.ts +++ b/public/app/features/org/state/actions.ts @@ -1,7 +1,8 @@ -import { ThunkResult } from 'app/types'; import { getBackendSrv } from '@grafana/runtime'; -import { organizationLoaded, userOrganizationsLoaded } from './reducers'; import { updateConfigurationSubtitle } from 'app/core/actions'; +import { ThunkResult } from 'app/types'; + +import { organizationLoaded, userOrganizationsLoaded } from './reducers'; type OrganizationDependencies = { getBackendSrv: typeof getBackendSrv }; diff --git a/public/app/features/org/state/reducers.test.ts b/public/app/features/org/state/reducers.test.ts index 00d47a26c6c..b99e2ce762f 100644 --- a/public/app/features/org/state/reducers.test.ts +++ b/public/app/features/org/state/reducers.test.ts @@ -1,5 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { OrganizationState, OrgRole } from '../../../types'; + import { initialState, organizationLoaded, diff --git a/public/app/features/panel/components/PanelDataErrorView.test.tsx b/public/app/features/panel/components/PanelDataErrorView.test.tsx index 10525bf0751..3d8ed667d61 100644 --- a/public/app/features/panel/components/PanelDataErrorView.test.tsx +++ b/public/app/features/panel/components/PanelDataErrorView.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import { defaultsDeep } from 'lodash'; -import { PanelDataErrorView } from './PanelDataErrorView'; -import { PanelDataErrorViewProps } from '@grafana/runtime'; -import { getDefaultTimeRange, LoadingState } from '@grafana/data'; +import React from 'react'; import { Provider } from 'react-redux'; + +import { getDefaultTimeRange, LoadingState } from '@grafana/data'; +import { PanelDataErrorViewProps } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; +import { PanelDataErrorView } from './PanelDataErrorView'; + describe('PanelDataErrorView', () => { it('show No data when there is no data', () => { renderWithProps(); diff --git a/public/app/features/panel/components/PanelDataErrorView.tsx b/public/app/features/panel/components/PanelDataErrorView.tsx index 55bb7aa50ca..a58848c432c 100644 --- a/public/app/features/panel/components/PanelDataErrorView.tsx +++ b/public/app/features/panel/components/PanelDataErrorView.tsx @@ -1,16 +1,18 @@ -import React from 'react'; -import { CoreApp, GrafanaTheme2, PanelDataSummary, VisualizationSuggestionsBuilder } from '@grafana/data'; -import { usePanelContext, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; -import { PanelDataErrorViewProps } from '@grafana/runtime'; -import { CardButton } from 'app/core/components/CardButton'; +import React from 'react'; import { useDispatch } from 'react-redux'; -import { toggleVizPicker } from 'app/features/dashboard/components/PanelEditor/state/reducers'; -import { changePanelPlugin } from '../state/actions'; -import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import store from 'app/core/store'; + +import { CoreApp, GrafanaTheme2, PanelDataSummary, VisualizationSuggestionsBuilder } from '@grafana/data'; +import { PanelDataErrorViewProps } from '@grafana/runtime'; +import { usePanelContext, useStyles2 } from '@grafana/ui'; +import { CardButton } from 'app/core/components/CardButton'; import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants'; +import store from 'app/core/store'; +import { toggleVizPicker } from 'app/features/dashboard/components/PanelEditor/state/reducers'; import { VisualizationSelectPaneTab } from 'app/features/dashboard/components/PanelEditor/types'; +import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; + +import { changePanelPlugin } from '../state/actions'; export function PanelDataErrorView(props: PanelDataErrorViewProps) { const styles = useStyles2(getStyles); diff --git a/public/app/features/panel/components/PanelPluginError.tsx b/public/app/features/panel/components/PanelPluginError.tsx index 1e001613ffc..0ce26af720f 100644 --- a/public/app/features/panel/components/PanelPluginError.tsx +++ b/public/app/features/panel/components/PanelPluginError.tsx @@ -2,9 +2,9 @@ import React, { PureComponent, ReactNode } from 'react'; // Types -import { AppNotificationSeverity } from 'app/types'; -import { Alert } from '@grafana/ui'; import { PanelProps, PanelPlugin, PluginType, PanelPluginMeta } from '@grafana/data'; +import { Alert } from '@grafana/ui'; +import { AppNotificationSeverity } from 'app/types'; interface Props { title: string; diff --git a/public/app/features/panel/components/PanelRenderer.tsx b/public/app/features/panel/components/PanelRenderer.tsx index 72ceb49bafd..98a86b3a209 100644 --- a/public/app/features/panel/components/PanelRenderer.tsx +++ b/public/app/features/panel/components/PanelRenderer.tsx @@ -1,11 +1,13 @@ import React, { useState, useMemo, useEffect, useRef } from 'react'; +import { useAsync } from 'react-use'; + import { applyFieldOverrides, FieldConfigSource, getTimeZone, PanelData, PanelPlugin } from '@grafana/data'; import { PanelRendererProps } from '@grafana/runtime'; +import { ErrorBoundaryAlert, useTheme2 } from '@grafana/ui'; import { appEvents } from 'app/core/core'; -import { useAsync } from 'react-use'; + import { getPanelOptionsWithDefaults, OptionDefaults } from '../../dashboard/state/getPanelOptionsWithDefaults'; import { importPanelPlugin } from '../../plugins/importPanelPlugin'; -import { ErrorBoundaryAlert, useTheme2 } from '@grafana/ui'; const defaultFieldConfig = { defaults: {}, overrides: [] }; diff --git a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx index 9c2c93f6f9a..7b3c9c57b5f 100644 --- a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx @@ -1,8 +1,9 @@ -import React, { MouseEventHandler } from 'react'; -import { GrafanaTheme2, isUnsignedPluginSignature, PanelPluginMeta, PluginState } from '@grafana/data'; -import { IconButton, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; import { css, cx } from '@emotion/css'; +import React, { MouseEventHandler } from 'react'; + +import { GrafanaTheme2, isUnsignedPluginSignature, PanelPluginMeta, PluginState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { IconButton, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; interface Props { diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx index 3305e1f64a2..99725d694fd 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx @@ -1,11 +1,14 @@ -import React, { CSSProperties } from 'react'; -import { GrafanaTheme2, PanelData, VisualizationSuggestion } from '@grafana/data'; -import { PanelRenderer } from '../PanelRenderer'; import { css, cx } from '@emotion/css'; -import { Tooltip, useStyles2 } from '@grafana/ui'; -import { VizTypeChangeDetails } from './types'; -import { selectors } from '@grafana/e2e-selectors'; import { cloneDeep } from 'lodash'; +import React, { CSSProperties } from 'react'; + +import { GrafanaTheme2, PanelData, VisualizationSuggestion } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Tooltip, useStyles2 } from '@grafana/ui'; + +import { PanelRenderer } from '../PanelRenderer'; + +import { VizTypeChangeDetails } from './types'; export interface Props { data: PanelData; diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index f8ecb38c7c3..14cd6bbed8b 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, PanelData, PanelPluginMeta, PanelModel, VisualizationSuggestion } from '@grafana/data'; import { css } from '@emotion/css'; -import { VizTypeChangeDetails } from './types'; -import { VisualizationSuggestionCard } from './VisualizationSuggestionCard'; -import { getAllSuggestions } from '../../state/getAllSuggestions'; +import React from 'react'; import { useAsync, useLocalStorage } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; +import { GrafanaTheme2, PanelData, PanelPluginMeta, PanelModel, VisualizationSuggestion } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +import { getAllSuggestions } from '../../state/getAllSuggestions'; + +import { VisualizationSuggestionCard } from './VisualizationSuggestionCard'; +import { VizTypeChangeDetails } from './types'; + export interface Props { current: PanelPluginMeta; data?: PanelData; diff --git a/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx b/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx index 01cb66ffd57..76f6687e0e6 100644 --- a/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx +++ b/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx @@ -1,9 +1,12 @@ -import React, { useMemo } from 'react'; -import { VizTypePickerPlugin } from './VizTypePickerPlugin'; -import { EmptySearchResult, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, PanelData, PanelPluginMeta } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; + +import { GrafanaTheme2, PanelData, PanelPluginMeta } from '@grafana/data'; +import { EmptySearchResult, useStyles2 } from '@grafana/ui'; + import { filterPluginList, getAllPanelPluginMeta } from '../../state/util'; + +import { VizTypePickerPlugin } from './VizTypePickerPlugin'; import { VizTypeChangeDetails } from './types'; export interface Props { diff --git a/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx b/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx index c39c9582a97..c792da0f90e 100644 --- a/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx +++ b/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx @@ -1,5 +1,7 @@ import React, { MouseEventHandler } from 'react'; + import { PanelPluginMeta } from '@grafana/data'; + import { PanelTypeCard } from './PanelTypeCard'; interface Props { diff --git a/public/app/features/panel/panellinks/linkSuppliers.test.ts b/public/app/features/panel/panellinks/linkSuppliers.test.ts index 69f44e4af14..2bdf1977972 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.test.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.test.ts @@ -1,8 +1,10 @@ -import { getFieldLinksSupplier } from './linkSuppliers'; import { applyFieldOverrides, createTheme, DataFrameView, dateTime, FieldDisplay, toDataFrame } from '@grafana/data'; -import { getLinkSrv, LinkService, LinkSrv, setLinkSrv } from './link_srv'; + import { TemplateSrv } from '../../templating/template_srv'; +import { getFieldLinksSupplier } from './linkSuppliers'; +import { getLinkSrv, LinkService, LinkSrv, setLinkSrv } from './link_srv'; + // We do not need more here and TimeSrv is hard to setup fully. jest.mock('app/features/dashboard/services/TimeSrv', () => ({ getTimeSrv: () => ({ diff --git a/public/app/features/panel/panellinks/linkSuppliers.ts b/public/app/features/panel/panellinks/linkSuppliers.ts index 65f2d075b3d..f34da404f94 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.ts @@ -1,4 +1,3 @@ -import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { DataLink, DisplayValue, @@ -12,6 +11,8 @@ import { ScopedVar, ScopedVars, } from '@grafana/data'; +import { PanelModel } from 'app/features/dashboard/state/PanelModel'; + import { getLinkSrv } from './link_srv'; interface SeriesVars { diff --git a/public/app/features/panel/panellinks/link_srv.ts b/public/app/features/panel/panellinks/link_srv.ts index a803bd8ec0f..88477e5d60b 100644 --- a/public/app/features/panel/panellinks/link_srv.ts +++ b/public/app/features/panel/panellinks/link_srv.ts @@ -1,7 +1,5 @@ import { chain } from 'lodash'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { getTemplateSrv } from '@grafana/runtime'; -import { getConfig } from 'app/core/config'; + import { DataFrame, DataLink, @@ -21,6 +19,10 @@ import { VariableSuggestion, VariableSuggestionsScope, } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; +import { getConfig } from 'app/core/config'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; + import { getVariablesUrlParams } from '../../variables/getAllVariableValuesForUrl'; const timeRangeVars = [ diff --git a/public/app/features/panel/panellinks/specs/link_srv.test.ts b/public/app/features/panel/panellinks/specs/link_srv.test.ts index b2b30096045..791a95d7441 100644 --- a/public/app/features/panel/panellinks/specs/link_srv.test.ts +++ b/public/app/features/panel/panellinks/specs/link_srv.test.ts @@ -1,12 +1,13 @@ import { FieldType, locationUtil, toDataFrame, VariableOrigin } from '@grafana/data'; import { setTemplateSrv } from '@grafana/runtime'; -import { getDataFrameVars, LinkSrv } from '../link_srv'; import { getTimeSrv, setTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { variableAdapters } from 'app/features/variables/adapters'; import { createQueryVariableAdapter } from 'app/features/variables/query/adapter'; -import { updateConfig } from '../../../../core/config'; + import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv'; +import { updateConfig } from '../../../../core/config'; +import { getDataFrameVars, LinkSrv } from '../link_srv'; jest.mock('app/core/core', () => ({ appEvents: { diff --git a/public/app/features/panel/state/actions.test.ts b/public/app/features/panel/state/actions.test.ts index 65cd5bcfbc0..beb0687757b 100644 --- a/public/app/features/panel/state/actions.test.ts +++ b/public/app/features/panel/state/actions.test.ts @@ -1,11 +1,14 @@ +import { mockStandardFieldConfigOptions } from 'test/helpers/fieldConfig'; + +import { standardEditorsRegistry, standardFieldConfigEditorRegistry } from '@grafana/data'; import { PanelModel } from 'app/features/dashboard/state'; -import { thunkTester } from '../../../../test/core/thunk/thunkTester'; -import { changePanelPlugin } from './actions'; -import { panelModelAndPluginReady } from './reducers'; import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks'; import { panelPluginLoaded } from 'app/features/plugins/admin/state/actions'; -import { standardEditorsRegistry, standardFieldConfigEditorRegistry } from '@grafana/data'; -import { mockStandardFieldConfigOptions } from 'test/helpers/fieldConfig'; + +import { thunkTester } from '../../../../test/core/thunk/thunkTester'; + +import { changePanelPlugin } from './actions'; +import { panelModelAndPluginReady } from './reducers'; jest.mock('app/features/plugins/importPanelPlugin', () => { return { diff --git a/public/app/features/panel/state/actions.ts b/public/app/features/panel/state/actions.ts index 0864c763632..1bfb01dac2b 100644 --- a/public/app/features/panel/state/actions.ts +++ b/public/app/features/panel/state/actions.ts @@ -1,7 +1,13 @@ -import { getPanelPluginNotFound } from 'app/features/panel/components/PanelPluginError'; +import { DataTransformerConfig, FieldConfigSource } from '@grafana/data'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; +import { getPanelOptionsWithDefaults } from 'app/features/dashboard/state/getPanelOptionsWithDefaults'; +import { LibraryElementDTO } from 'app/features/library-panels/types'; +import { toPanelModelLibraryPanel } from 'app/features/library-panels/utils'; +import { getPanelPluginNotFound } from 'app/features/panel/components/PanelPluginError'; import { loadPanelPlugin } from 'app/features/plugins/admin/state/actions'; import { ThunkResult } from 'app/types'; +import { PanelOptionsChangedEvent, PanelQueriesChangedEvent } from 'app/types/events'; + import { changePanelKey, cleanUpAngularComponent, @@ -9,11 +15,6 @@ import { removePanel, removePanels, } from './reducers'; -import { LibraryElementDTO } from 'app/features/library-panels/types'; -import { toPanelModelLibraryPanel } from 'app/features/library-panels/utils'; -import { PanelOptionsChangedEvent, PanelQueriesChangedEvent } from 'app/types/events'; -import { DataTransformerConfig, FieldConfigSource } from '@grafana/data'; -import { getPanelOptionsWithDefaults } from 'app/features/dashboard/state/getPanelOptionsWithDefaults'; export function initPanelState(panel: PanelModel): ThunkResult { return async (dispatch, getStore) => { diff --git a/public/app/features/panel/state/getAllSuggestions.test.ts b/public/app/features/panel/state/getAllSuggestions.test.ts index 13200f1b454..dcb489ff84a 100644 --- a/public/app/features/panel/state/getAllSuggestions.test.ts +++ b/public/app/features/panel/state/getAllSuggestions.test.ts @@ -10,6 +10,7 @@ import { } from '@grafana/data'; import { config } from 'app/core/config'; import { SuggestionName } from 'app/types/suggestions'; + import { getAllSuggestions, panelsToCheckFirst } from './getAllSuggestions'; jest.unmock('app/core/core'); diff --git a/public/app/features/panel/state/reducers.ts b/public/app/features/panel/state/reducers.ts index b809a223ba4..7c61f4f46cc 100644 --- a/public/app/features/panel/state/reducers.ts +++ b/public/app/features/panel/state/reducers.ts @@ -1,6 +1,7 @@ import { createSlice, Draft, PayloadAction } from '@reduxjs/toolkit'; -import { AngularComponent } from '@grafana/runtime'; + import { PanelPlugin } from '@grafana/data'; +import { AngularComponent } from '@grafana/runtime'; export type RootPanelsState = Record; diff --git a/public/app/features/panel/state/selectors.ts b/public/app/features/panel/state/selectors.ts index af4aac6faee..5bfaccfe5f7 100644 --- a/public/app/features/panel/state/selectors.ts +++ b/public/app/features/panel/state/selectors.ts @@ -1,5 +1,6 @@ import { PanelModel } from 'app/features/dashboard/state'; import { StoreState } from 'app/types'; + import { PanelState } from './reducers'; export function getPanelStateForModel(state: StoreState, model: PanelModel): PanelState | undefined { diff --git a/public/app/features/panel/state/utils.test.ts b/public/app/features/panel/state/utils.test.ts index 6735aeb9a98..f37af140f32 100644 --- a/public/app/features/panel/state/utils.test.ts +++ b/public/app/features/panel/state/utils.test.ts @@ -1,4 +1,5 @@ import { PanelPluginMeta, escapeStringForRegex } from '@grafana/data'; + import { filterPluginList } from './util'; describe('panel state utils', () => { diff --git a/public/app/features/playlist/EmptyQueryListBanner.tsx b/public/app/features/playlist/EmptyQueryListBanner.tsx index 6a098c21d30..2a2e7967cb1 100644 --- a/public/app/features/playlist/EmptyQueryListBanner.tsx +++ b/public/app/features/playlist/EmptyQueryListBanner.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; export const EmptyQueryListBanner = () => { const styles = useStyles2(getStyles); diff --git a/public/app/features/playlist/PlaylistEditPage.test.tsx b/public/app/features/playlist/PlaylistEditPage.test.tsx index 2766c0fe6c4..4815eb4e183 100644 --- a/public/app/features/playlist/PlaylistEditPage.test.tsx +++ b/public/app/features/playlist/PlaylistEditPage.test.tsx @@ -1,11 +1,12 @@ -import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; - -import { Playlist } from './types'; -import { PlaylistEditPage } from './PlaylistEditPage'; -import { backendSrv } from 'app/core/services/backend_srv'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { locationService } from '@grafana/runtime'; +import { backendSrv } from 'app/core/services/backend_srv'; + +import { PlaylistEditPage } from './PlaylistEditPage'; +import { Playlist } from './types'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as any), @@ -69,10 +70,10 @@ describe('PlaylistEditPage', () => { const { putMock } = await getTestContext(); expect(locationService.getLocation().pathname).toEqual('/'); - userEvent.clear(screen.getByRole('textbox', { name: /playlist name/i })); - userEvent.type(screen.getByRole('textbox', { name: /playlist name/i }), 'A Name'); - userEvent.clear(screen.getByRole('textbox', { name: /playlist interval/i })); - userEvent.type(screen.getByRole('textbox', { name: /playlist interval/i }), '10s'); + await userEvent.clear(screen.getByRole('textbox', { name: /playlist name/i })); + await userEvent.type(screen.getByRole('textbox', { name: /playlist name/i }), 'A Name'); + await userEvent.clear(screen.getByRole('textbox', { name: /playlist interval/i })); + await userEvent.type(screen.getByRole('textbox', { name: /playlist interval/i }), '10s'); fireEvent.submit(screen.getByRole('button', { name: /save/i })); await waitFor(() => expect(putMock).toHaveBeenCalledTimes(1)); expect(putMock).toHaveBeenCalledWith('/api/playlists/1', { diff --git a/public/app/features/playlist/PlaylistEditPage.tsx b/public/app/features/playlist/PlaylistEditPage.tsx index 2643a95cee7..15855980dd3 100644 --- a/public/app/features/playlist/PlaylistEditPage.tsx +++ b/public/app/features/playlist/PlaylistEditPage.tsx @@ -1,18 +1,20 @@ import React, { FC } from 'react'; import { connect, MapStateToProps } from 'react-redux'; + import { NavModel } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; - import Page from 'app/core/components/Page/Page'; -import { StoreState } from 'app/types'; -import { GrafanaRouteComponentProps } from '../../core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; + +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; + import { PlaylistForm } from './PlaylistForm'; import { updatePlaylist } from './api'; +import { getPlaylistStyles } from './styles'; import { Playlist } from './types'; import { usePlaylist } from './usePlaylist'; -import { getPlaylistStyles } from './styles'; interface ConnectedProps { navModel: NavModel; diff --git a/public/app/features/playlist/PlaylistForm.test.tsx b/public/app/features/playlist/PlaylistForm.test.tsx index b401838b698..d257436645d 100644 --- a/public/app/features/playlist/PlaylistForm.test.tsx +++ b/public/app/features/playlist/PlaylistForm.test.tsx @@ -1,10 +1,10 @@ -import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { within } from '@testing-library/dom'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; -import { Playlist } from './types'; import { PlaylistForm } from './PlaylistForm'; +import { Playlist } from './types'; jest.mock('../../core/components/TagFilter/TagFilter', () => ({ TagFilter: () => { @@ -96,11 +96,11 @@ describe('PlaylistForm', () => { }); describe('when deleting a playlist item', () => { - it('then the item should be removed and other items should be correct', () => { + it('then the item should be removed and other items should be correct', async () => { getTestContext(playlist); expect(rows()).toHaveLength(3); - userEvent.click(within(rows()[2]).getByRole('button', { name: /delete playlist item/i })); + await userEvent.click(within(rows()[2]).getByRole('button', { name: /delete playlist item/i })); expect(rows()).toHaveLength(2); expectCorrectRow({ index: 0, type: 'id', title: 'first item', first: true }); expectCorrectRow({ index: 1, type: 'id', title: 'middle item', last: true }); @@ -108,10 +108,10 @@ describe('PlaylistForm', () => { }); describe('when moving a playlist item up', () => { - it('then the item should be removed and other items should be correct', () => { + it('then the item should be removed and other items should be correct', async () => { getTestContext(playlist); - userEvent.click(within(rows()[2]).getByRole('button', { name: /move playlist item order up/i })); + await userEvent.click(within(rows()[2]).getByRole('button', { name: /move playlist item order up/i })); expectCorrectRow({ index: 0, type: 'id', title: 'first item', first: true }); expectCorrectRow({ index: 1, type: 'tag', title: 'last item' }); expectCorrectRow({ index: 2, type: 'id', title: 'middle item', last: true }); @@ -119,10 +119,10 @@ describe('PlaylistForm', () => { }); describe('when moving a playlist item down', () => { - it('then the item should be removed and other items should be correct', () => { + it('then the item should be removed and other items should be correct', async () => { getTestContext(playlist); - userEvent.click(within(rows()[0]).getByRole('button', { name: /move playlist item order down/i })); + await userEvent.click(within(rows()[0]).getByRole('button', { name: /move playlist item order down/i })); expectCorrectRow({ index: 0, type: 'id', title: 'middle item', first: true }); expectCorrectRow({ index: 1, type: 'id', title: 'first item' }); expectCorrectRow({ index: 2, type: 'tag', title: 'last item', last: true }); @@ -152,7 +152,7 @@ describe('PlaylistForm', () => { it('then an alert should appear and nothing should be submitted', async () => { const { onSubmitMock } = getTestContext(playlist); - userEvent.clear(screen.getByRole('textbox', { name: /playlist interval/i })); + await userEvent.clear(screen.getByRole('textbox', { name: /playlist interval/i })); fireEvent.submit(screen.getByRole('button', { name: /save/i })); expect(await screen.findAllByRole('alert')).toHaveLength(1); expect(onSubmitMock).not.toHaveBeenCalled(); diff --git a/public/app/features/playlist/PlaylistForm.tsx b/public/app/features/playlist/PlaylistForm.tsx index 6e5b6662c99..b648b151330 100644 --- a/public/app/features/playlist/PlaylistForm.tsx +++ b/public/app/features/playlist/PlaylistForm.tsx @@ -1,14 +1,16 @@ import React, { FC } from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; import { Button, Field, Form, HorizontalGroup, Input, LinkButton } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; +import { DashboardPickerByID } from 'app/core/components/editors/DashboardPickerByID'; -import { Playlist } from './types'; import { TagFilter } from '../../core/components/TagFilter/TagFilter'; import { SearchSrv } from '../../core/services/search_srv'; -import { usePlaylistItems } from './usePlaylistItems'; + import { PlaylistTable } from './PlaylistTable'; -import { DashboardPickerByID } from 'app/core/components/editors/DashboardPickerByID'; +import { Playlist } from './types'; +import { usePlaylistItems } from './usePlaylistItems'; interface PlaylistFormProps { onSubmit: (playlist: Playlist) => void; diff --git a/public/app/features/playlist/PlaylistNewPage.test.tsx b/public/app/features/playlist/PlaylistNewPage.test.tsx index 487bd152607..9dbb9cadab0 100644 --- a/public/app/features/playlist/PlaylistNewPage.test.tsx +++ b/public/app/features/playlist/PlaylistNewPage.test.tsx @@ -1,12 +1,14 @@ -import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; -import { Playlist } from './types'; -import { PlaylistNewPage } from './PlaylistNewPage'; -import { backendSrv } from '../../core/services/backend_srv'; import { locationService } from '@grafana/runtime'; +import { backendSrv } from '../../core/services/backend_srv'; + +import { PlaylistNewPage } from './PlaylistNewPage'; +import { Playlist } from './types'; + jest.mock('./usePlaylist', () => ({ // so we don't need to add dashboard items in test usePlaylist: jest.fn().mockReturnValue({ @@ -66,7 +68,7 @@ describe('PlaylistNewPage', () => { const { backendSrvMock } = getTestContext(); expect(locationService.getLocation().pathname).toEqual('/'); - userEvent.type(screen.getByRole('textbox', { name: /playlist name/i }), 'A Name'); + await userEvent.type(screen.getByRole('textbox', { name: /playlist name/i }), 'A Name'); fireEvent.submit(screen.getByRole('button', { name: /save/i })); await waitFor(() => expect(backendSrvMock).toHaveBeenCalledTimes(1)); expect(backendSrvMock).toHaveBeenCalledWith('/api/playlists', { diff --git a/public/app/features/playlist/PlaylistNewPage.tsx b/public/app/features/playlist/PlaylistNewPage.tsx index 29449d91044..c08d288ec99 100644 --- a/public/app/features/playlist/PlaylistNewPage.tsx +++ b/public/app/features/playlist/PlaylistNewPage.tsx @@ -1,18 +1,20 @@ import React, { FC } from 'react'; import { connect, MapStateToProps } from 'react-redux'; + import { NavModel } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; - import Page from 'app/core/components/Page/Page'; -import { StoreState } from 'app/types'; -import { GrafanaRouteComponentProps } from '../../core/navigation/types'; import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; + +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; + import { PlaylistForm } from './PlaylistForm'; import { createPlaylist } from './api'; +import { getPlaylistStyles } from './styles'; import { Playlist } from './types'; import { usePlaylist } from './usePlaylist'; -import { getPlaylistStyles } from './styles'; interface ConnectedProps { navModel: NavModel; diff --git a/public/app/features/playlist/PlaylistPage.test.tsx b/public/app/features/playlist/PlaylistPage.test.tsx index f60149d3df9..861bd8759e0 100644 --- a/public/app/features/playlist/PlaylistPage.test.tsx +++ b/public/app/features/playlist/PlaylistPage.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, waitFor } from '@testing-library/react'; -import { PlaylistPage, PlaylistPageProps } from './PlaylistPage'; +import React from 'react'; + import { locationService } from '../../../../packages/grafana-runtime/src'; +import { PlaylistPage, PlaylistPageProps } from './PlaylistPage'; + const fnMock = jest.fn(); jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/features/playlist/PlaylistPage.tsx b/public/app/features/playlist/PlaylistPage.tsx index 3477bc489d8..715de58040d 100644 --- a/public/app/features/playlist/PlaylistPage.tsx +++ b/public/app/features/playlist/PlaylistPage.tsx @@ -1,19 +1,22 @@ import React, { FC, useState } from 'react'; import { connect, MapStateToProps } from 'react-redux'; -import { NavModel } from '@grafana/data'; -import Page from 'app/core/components/Page/Page'; -import { StoreState } from 'app/types'; -import { GrafanaRouteComponentProps } from '../../core/navigation/types'; -import { getNavModel } from 'app/core/selectors/navModel'; import { useDebounce } from 'react-use'; -import { PlaylistDTO } from './types'; + +import { NavModel } from '@grafana/data'; import { ConfirmModal } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; + import EmptyListCTA from '../../core/components/EmptyListCTA/EmptyListCTA'; -import { deletePlaylist, getAllPlaylist } from './api'; -import { StartModal } from './StartModal'; -import { PlaylistPageList } from './PlaylistPageList'; +import { GrafanaRouteComponentProps } from '../../core/navigation/types'; + import { EmptyQueryListBanner } from './EmptyQueryListBanner'; +import { PlaylistPageList } from './PlaylistPageList'; +import { StartModal } from './StartModal'; +import { deletePlaylist, getAllPlaylist } from './api'; +import { PlaylistDTO } from './types'; interface ConnectedProps { navModel: NavModel; diff --git a/public/app/features/playlist/PlaylistPageList.tsx b/public/app/features/playlist/PlaylistPageList.tsx index 79adbe7e964..d8b77397b05 100644 --- a/public/app/features/playlist/PlaylistPageList.tsx +++ b/public/app/features/playlist/PlaylistPageList.tsx @@ -1,11 +1,14 @@ +import { css } from '@emotion/css'; import React from 'react'; -import { PlaylistDTO } from './types'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, Card, LinkButton, ModalsController, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; -import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; + import { DashNavButton } from '../dashboard/components/DashNav/DashNavButton'; + import { ShareModal } from './ShareModal'; +import { PlaylistDTO } from './types'; interface Props { setStartPlaylist: (playlistItem: PlaylistDTO) => void; diff --git a/public/app/features/playlist/PlaylistSrv.test.ts b/public/app/features/playlist/PlaylistSrv.test.ts index d886883e9c4..b5238fe9c4d 100644 --- a/public/app/features/playlist/PlaylistSrv.test.ts +++ b/public/app/features/playlist/PlaylistSrv.test.ts @@ -1,8 +1,10 @@ // @ts-ignore import configureMockStore from 'redux-mock-store'; -import { PlaylistSrv } from './PlaylistSrv'; -import { setStore } from 'app/store/store'; + import { locationService } from '@grafana/runtime'; +import { setStore } from 'app/store/store'; + +import { PlaylistSrv } from './PlaylistSrv'; const getMock = jest.fn(); diff --git a/public/app/features/playlist/PlaylistSrv.ts b/public/app/features/playlist/PlaylistSrv.ts index b959d5eed17..a2d1704c565 100644 --- a/public/app/features/playlist/PlaylistSrv.ts +++ b/public/app/features/playlist/PlaylistSrv.ts @@ -1,10 +1,10 @@ // Libraries +import { Location } from 'history'; import { pickBy } from 'lodash'; // Utils -import { getBackendSrv, locationService } from '@grafana/runtime'; import { locationUtil, urlUtil, rangeUtil } from '@grafana/data'; -import { Location } from 'history'; +import { getBackendSrv, locationService } from '@grafana/runtime'; export const queryParamsToPreserve: { [key: string]: boolean } = { kiosk: true, diff --git a/public/app/features/playlist/PlaylistStartPage.tsx b/public/app/features/playlist/PlaylistStartPage.tsx index 5ca538b096a..07741b62d62 100644 --- a/public/app/features/playlist/PlaylistStartPage.tsx +++ b/public/app/features/playlist/PlaylistStartPage.tsx @@ -1,5 +1,7 @@ import { FC } from 'react'; + import { GrafanaRouteComponentProps } from '../../core/navigation/types'; + import { playlistSrv } from './PlaylistSrv'; interface Props extends GrafanaRouteComponentProps<{ id: string }> {} diff --git a/public/app/features/playlist/PlaylistTableRow.tsx b/public/app/features/playlist/PlaylistTableRow.tsx index 79ec985c757..ca34695e4ac 100644 --- a/public/app/features/playlist/PlaylistTableRow.tsx +++ b/public/app/features/playlist/PlaylistTableRow.tsx @@ -1,11 +1,13 @@ -import React, { FC, MouseEvent } from 'react'; import { css, cx } from '@emotion/css'; -import { Icon, IconButton, useStyles } from '@grafana/ui'; +import React, { FC, MouseEvent } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Icon, IconButton, useStyles } from '@grafana/ui'; import { TagBadge } from '../../core/components/TagFilter/TagBadge'; + import { PlaylistItem } from './types'; -import { selectors } from '@grafana/e2e-selectors'; interface PlaylistTableRowProps { first: boolean; diff --git a/public/app/features/playlist/ShareModal.tsx b/public/app/features/playlist/ShareModal.tsx index e43450525d6..ce312acfe6d 100644 --- a/public/app/features/playlist/ShareModal.tsx +++ b/public/app/features/playlist/ShareModal.tsx @@ -1,8 +1,11 @@ import React, { useState } from 'react'; + import { AppEvents, SelectableValue, UrlQueryMap, urlUtil } from '@grafana/data'; import { Checkbox, ClipboardButton, Field, FieldSet, Icon, Input, Modal, RadioButtonGroup } from '@grafana/ui'; import appEvents from 'app/core/app_events'; + import { buildBaseUrl } from '../dashboard/components/ShareModal/utils'; + import { PlaylistMode } from './types'; interface ShareModalProps { diff --git a/public/app/features/playlist/StartModal.tsx b/public/app/features/playlist/StartModal.tsx index ffe772e91df..8d9e85443ab 100644 --- a/public/app/features/playlist/StartModal.tsx +++ b/public/app/features/playlist/StartModal.tsx @@ -1,9 +1,11 @@ import React, { FC, useState } from 'react'; + import { SelectableValue, urlUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { PlaylistDTO, PlaylistMode } from './types'; import { Button, Checkbox, Field, FieldSet, Modal, RadioButtonGroup } from '@grafana/ui'; +import { PlaylistDTO, PlaylistMode } from './types'; + export interface StartModalProps { playlist: PlaylistDTO; onDismiss: () => void; diff --git a/public/app/features/playlist/api.ts b/public/app/features/playlist/api.ts index d7b6f2739a0..cab3e54859a 100644 --- a/public/app/features/playlist/api.ts +++ b/public/app/features/playlist/api.ts @@ -1,9 +1,10 @@ import { getBackendSrv } from '@grafana/runtime'; -import { Playlist, PlaylistDTO } from './types'; -import { dispatch } from '../../store/store'; import { notifyApp } from '../../core/actions'; import { createErrorNotification, createSuccessNotification } from '../../core/copy/appNotification'; +import { dispatch } from '../../store/store'; + +import { Playlist, PlaylistDTO } from './types'; export async function createPlaylist(playlist: Playlist) { await withErrorHandling(() => getBackendSrv().post('/api/playlists', playlist)); diff --git a/public/app/features/playlist/styles.ts b/public/app/features/playlist/styles.ts index 4a301594464..0d04725bae5 100644 --- a/public/app/features/playlist/styles.ts +++ b/public/app/features/playlist/styles.ts @@ -1,6 +1,7 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; + export function getPlaylistStyles(theme: GrafanaTheme2) { return { description: css` diff --git a/public/app/features/playlist/usePlaylist.tsx b/public/app/features/playlist/usePlaylist.tsx index b3e7e872889..6cb9d0f006f 100644 --- a/public/app/features/playlist/usePlaylist.tsx +++ b/public/app/features/playlist/usePlaylist.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; -import { Playlist } from './types'; + import { getPlaylist } from './api'; +import { Playlist } from './types'; export function usePlaylist(playlistId?: number) { const [playlist, setPlaylist] = useState({ items: [], interval: '5m', name: '' }); diff --git a/public/app/features/playlist/usePlaylistItems.tsx b/public/app/features/playlist/usePlaylistItems.tsx index 576879190d4..83e58132705 100644 --- a/public/app/features/playlist/usePlaylistItems.tsx +++ b/public/app/features/playlist/usePlaylistItems.tsx @@ -1,6 +1,7 @@ -import { DashboardPickerItem } from 'app/core/components/editors/DashboardPickerByID'; import { useCallback, useState } from 'react'; +import { DashboardPickerItem } from 'app/core/components/editors/DashboardPickerByID'; + import { PlaylistItem } from './types'; export function usePlaylistItems(playlistItems?: PlaylistItem[]) { diff --git a/public/app/features/plugins/__mocks__/pluginMocks.ts b/public/app/features/plugins/__mocks__/pluginMocks.ts index dbbd1fb89ad..372e2f49c2e 100644 --- a/public/app/features/plugins/__mocks__/pluginMocks.ts +++ b/public/app/features/plugins/__mocks__/pluginMocks.ts @@ -1,6 +1,7 @@ -import { defaultsDeep } from 'lodash'; -import { PanelPluginMeta, PluginMeta, PluginType, PanelPlugin, PanelProps } from '@grafana/data'; import { ComponentType } from 'enzyme'; +import { defaultsDeep } from 'lodash'; + +import { PanelPluginMeta, PluginMeta, PluginType, PanelPlugin, PanelProps } from '@grafana/data'; export const getMockPlugins = (amount: number): PluginMeta[] => { const plugins = []; diff --git a/public/app/features/plugins/admin/__mocks__/mockHelpers.ts b/public/app/features/plugins/admin/__mocks__/mockHelpers.ts index ac18a598857..096f7384dc2 100644 --- a/public/app/features/plugins/admin/__mocks__/mockHelpers.ts +++ b/public/app/features/plugins/admin/__mocks__/mockHelpers.ts @@ -1,5 +1,7 @@ import { setBackendSrv } from '@grafana/runtime'; + import { API_ROOT, GCOM_API_ROOT } from '../constants'; +import * as permissions from '../permissions'; import { CatalogPlugin, LocalPlugin, @@ -9,10 +11,10 @@ import { RequestStatus, PluginListDisplayMode, } from '../types'; -import * as permissions from '../permissions'; -import remotePluginMock from './remotePlugin.mock'; -import localPluginMock from './localPlugin.mock'; + import catalogPluginMock from './catalogPlugin.mock'; +import localPluginMock from './localPlugin.mock'; +import remotePluginMock from './remotePlugin.mock'; // Returns a sample mock for a CatalogPlugin plugin with the possibility to extend it export const getCatalogPluginMock = (overrides?: Partial) => ({ ...catalogPluginMock, ...overrides }); diff --git a/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts b/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts index 903d6b7435b..1ea0ed50654 100644 --- a/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts +++ b/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts @@ -1,4 +1,5 @@ import { PluginSignatureType, PluginType } from '@grafana/data'; + import { RemotePlugin } from '../types'; // Copied from /api/gnet/plugins/alexanderzobnin-zabbix-app diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index fbca7d8a193..949209b42bc 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -1,8 +1,9 @@ -import { getBackendSrv } from '@grafana/runtime'; import { PluginError, PluginMeta, renderMarkdown } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; + import { API_ROOT, GCOM_API_ROOT } from './constants'; -import { LocalPlugin, RemotePlugin, CatalogPluginDetails, Version, PluginVersion } from './types'; import { isLocalPluginVisible, isRemotePluginVisible } from './helpers'; +import { LocalPlugin, RemotePlugin, CatalogPluginDetails, Version, PluginVersion } from './types'; export async function getPluginDetails(id: string): Promise { const remote = await getRemotePlugin(id); diff --git a/public/app/features/plugins/admin/components/AppConfigWrapper.tsx b/public/app/features/plugins/admin/components/AppConfigWrapper.tsx index 267455ed1d4..f2f71509e88 100644 --- a/public/app/features/plugins/admin/components/AppConfigWrapper.tsx +++ b/public/app/features/plugins/admin/components/AppConfigWrapper.tsx @@ -1,12 +1,11 @@ // Libraries -import React, { PureComponent } from 'react'; +import { css } from '@emotion/css'; import { cloneDeep, extend } from 'lodash'; +import React, { PureComponent } from 'react'; -import { Button } from '@grafana/ui'; import { PluginMeta, AppPlugin, deprecationWarning } from '@grafana/data'; import { AngularComponent, getAngularLoader, getBackendSrv } from '@grafana/runtime'; - -import { css } from '@emotion/css'; +import { Button } from '@grafana/ui'; interface Props { app: AppPlugin; diff --git a/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx index 22178e64fe4..dbac4871b07 100644 --- a/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx +++ b/public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { PluginErrorCode } from '@grafana/data'; import { Badge } from '@grafana/ui'; diff --git a/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx index 345e077ba5f..7032ce2d22c 100644 --- a/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx +++ b/public/app/features/plugins/admin/components/Badges/PluginEnterpriseBadge.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import { Badge, Button, HorizontalGroup, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; -import { CatalogPlugin } from '../../types'; -import { getBadgeColor } from './sharedStyles'; + import { featureEnabled } from '@grafana/runtime'; +import { Badge, Button, HorizontalGroup, PluginSignatureBadge, useStyles2 } from '@grafana/ui'; + +import { CatalogPlugin } from '../../types'; + +import { getBadgeColor } from './sharedStyles'; type Props = { plugin: CatalogPlugin }; diff --git a/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx index dc035ec77ff..bc5c182af68 100644 --- a/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx +++ b/public/app/features/plugins/admin/components/Badges/PluginInstallBadge.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { Badge, useStyles2 } from '@grafana/ui'; + import { getBadgeColor } from './sharedStyles'; export function PluginInstalledBadge(): React.ReactElement { diff --git a/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx index 2c518c5179b..f726ac2fc26 100644 --- a/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx +++ b/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2, PluginType } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; + import { CatalogPlugin } from '../../types'; type Props = { diff --git a/public/app/features/plugins/admin/components/Badges/sharedStyles.ts b/public/app/features/plugins/admin/components/Badges/sharedStyles.ts index 26486c6ad57..9ad5e6521f4 100644 --- a/public/app/features/plugins/admin/components/Badges/sharedStyles.ts +++ b/public/app/features/plugins/admin/components/Badges/sharedStyles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; export const getBadgeColor = (theme: GrafanaTheme2) => css` diff --git a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithApp.tsx b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithApp.tsx index 08ebd079970..90e93f83ac4 100644 --- a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithApp.tsx +++ b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithApp.tsx @@ -1,8 +1,10 @@ +import React from 'react'; + import { PluginMeta } from '@grafana/data'; import { Button } from '@grafana/ui'; -import { usePluginConfig } from '../../hooks/usePluginConfig'; + import { updatePluginSettings } from '../../api'; -import React from 'react'; +import { usePluginConfig } from '../../hooks/usePluginConfig'; import { CatalogPlugin } from '../../types'; type Props = { diff --git a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx index 55e7c3f29ae..7f4e9cdf903 100644 --- a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx +++ b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx @@ -1,8 +1,10 @@ +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; + import { DataSourcePluginMeta } from '@grafana/data'; import { Button } from '@grafana/ui'; import { addDataSource } from 'app/features/datasources/state/actions'; -import React, { useCallback } from 'react'; -import { useDispatch } from 'react-redux'; + import { isDataSourceEditor } from '../../permissions'; import { CatalogPlugin } from '../../types'; diff --git a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithPlugin.tsx b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithPlugin.tsx index aa251e1d07e..260633c79b1 100644 --- a/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithPlugin.tsx +++ b/public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithPlugin.tsx @@ -1,8 +1,11 @@ import React, { ReactElement } from 'react'; + import { PluginType } from '@grafana/data'; + import { CatalogPlugin } from '../../types'; -import { GetStartedWithDataSource } from './GetStartedWithDataSource'; + import { GetStartedWithApp } from './GetStartedWithApp'; +import { GetStartedWithDataSource } from './GetStartedWithDataSource'; type Props = { plugin: CatalogPlugin; diff --git a/public/app/features/plugins/admin/components/HorizontalGroup.tsx b/public/app/features/plugins/admin/components/HorizontalGroup.tsx index 5e0fbaa06b1..0d948f963c9 100644 --- a/public/app/features/plugins/admin/components/HorizontalGroup.tsx +++ b/public/app/features/plugins/admin/components/HorizontalGroup.tsx @@ -1,8 +1,8 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; -import { useTheme2 } from '@grafana/ui'; import { GrafanaTheme2 } from '@grafana/data'; +import { useTheme2 } from '@grafana/ui'; interface HorizontalGroupProps { children: React.ReactNode; diff --git a/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx b/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx index 5aa72575014..651d46b132b 100644 --- a/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { HorizontalGroup, LinkButton } from '@grafana/ui'; + import { getExternalManageLink } from '../../helpers'; import { PluginStatus } from '../../types'; diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx index 17cafd20c43..73235702496 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControls.tsx @@ -1,16 +1,17 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; +import { GrafanaTheme2, PluginType } from '@grafana/data'; import { config, featureEnabled } from '@grafana/runtime'; import { HorizontalGroup, Icon, LinkButton, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, PluginType } from '@grafana/data'; + +import { getExternalManageLink, isInstallControlsEnabled } from '../../helpers'; +import { isGrafanaAdmin } from '../../permissions'; +import { useIsRemotePluginsAvailable } from '../../state/hooks'; +import { CatalogPlugin, PluginStatus, Version } from '../../types'; import { ExternallyManagedButton } from './ExternallyManagedButton'; import { InstallControlsButton } from './InstallControlsButton'; -import { CatalogPlugin, PluginStatus, Version } from '../../types'; -import { getExternalManageLink, isInstallControlsEnabled } from '../../helpers'; -import { useIsRemotePluginsAvailable } from '../../state/hooks'; -import { isGrafanaAdmin } from '../../permissions'; interface Props { plugin: CatalogPlugin; diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx index ab18c18c9fc..6b47cb70014 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; + import { AppEvents } from '@grafana/data'; import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import { CatalogPlugin, PluginStatus, Version } from '../../types'; import { useInstallStatus, useUninstallStatus, useInstall, useUninstall } from '../../state/hooks'; +import { CatalogPlugin, PluginStatus, Version } from '../../types'; type InstallControlsButtonProps = { plugin: CatalogPlugin; diff --git a/public/app/features/plugins/admin/components/Loader.tsx b/public/app/features/plugins/admin/components/Loader.tsx index 1113f8e7215..2ce20da786d 100644 --- a/public/app/features/plugins/admin/components/Loader.tsx +++ b/public/app/features/plugins/admin/components/Loader.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { LoadingPlaceholder } from '@grafana/ui'; + import { Page } from './Page'; export interface Props { diff --git a/public/app/features/plugins/admin/components/Page.tsx b/public/app/features/plugins/admin/components/Page.tsx index fd3ac726632..199cb9939c3 100644 --- a/public/app/features/plugins/admin/components/Page.tsx +++ b/public/app/features/plugins/admin/components/Page.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; export const Page: React.FC = ({ children }) => { const styles = useStyles2(getStyles); diff --git a/public/app/features/plugins/admin/components/PluginDashboards.tsx b/public/app/features/plugins/admin/components/PluginDashboards.tsx index 734368722aa..464a84f4e18 100644 --- a/public/app/features/plugins/admin/components/PluginDashboards.tsx +++ b/public/app/features/plugins/admin/components/PluginDashboards.tsx @@ -1,12 +1,11 @@ +import { extend } from 'lodash'; import React, { PureComponent } from 'react'; -import { extend } from 'lodash'; - -import { PluginDashboard } from 'app/types'; +import { AppEvents, PluginMeta, DataSourceApi } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { appEvents } from 'app/core/core'; import DashboardsTable from 'app/features/datasources/DashboardsTable'; -import { AppEvents, PluginMeta, DataSourceApi } from '@grafana/data'; +import { PluginDashboard } from 'app/types'; interface Props { plugin: PluginMeta; diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx index 5608e976b42..790afe2a7f5 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx @@ -1,12 +1,13 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; import { AppPlugin, GrafanaTheme2, UrlQueryMap } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { CatalogPlugin, PluginTabIds } from '../types'; import { VersionList } from '../components/VersionList'; import { usePluginConfig } from '../hooks/usePluginConfig'; +import { CatalogPlugin, PluginTabIds } from '../types'; + import { AppConfigCtrlWrapper } from './AppConfigWrapper'; import { PluginDashboards } from './PluginDashboards'; diff --git a/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx b/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx index 12b6b6cf043..9f34a4b0ced 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx @@ -1,8 +1,10 @@ import React, { ReactElement } from 'react'; + import { PluginErrorCode } from '@grafana/data'; -import { Alert } from '@grafana/ui'; -import { CatalogPlugin } from '../types'; import { selectors } from '@grafana/e2e-selectors'; +import { Alert } from '@grafana/ui'; + +import { CatalogPlugin } from '../types'; type Props = { className?: string; diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx index 51f161901e4..4e8b77e58c8 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx @@ -1,16 +1,18 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, Icon, HorizontalGroup } from '@grafana/ui'; -import { InstallControls } from './InstallControls'; -import { PluginDetailsHeaderSignature } from './PluginDetailsHeaderSignature'; -import { PluginDetailsHeaderDependencies } from './PluginDetailsHeaderDependencies'; -import { PluginLogo } from './PluginLogo'; +import { getLatestCompatibleVersion } from '../helpers'; import { CatalogPlugin } from '../types'; + import { PluginDisabledBadge } from './Badges'; import { GetStartedWithPlugin } from './GetStartedWithPlugin'; -import { getLatestCompatibleVersion } from '../helpers'; +import { InstallControls } from './InstallControls'; +import { PluginDetailsHeaderDependencies } from './PluginDetailsHeaderDependencies'; +import { PluginDetailsHeaderSignature } from './PluginDetailsHeaderSignature'; +import { PluginLogo } from './PluginLogo'; type Props = { currentUrl: string; diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx index a0c6ae173d4..ba73cf57716 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, Icon } from '@grafana/ui'; + import { Version, CatalogPlugin, PluginIconName } from '../types'; type Props = { diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx index 5c73d09d048..f44452ed7d9 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2, PluginSignatureStatus } from '@grafana/data'; import { PluginSignatureBadge, useStyles2 } from '@grafana/ui'; -import { PluginSignatureDetailsBadge } from './PluginSignatureDetailsBadge'; + import { CatalogPlugin } from '../types'; +import { PluginSignatureDetailsBadge } from './PluginSignatureDetailsBadge'; + type Props = { plugin: CatalogPlugin; }; diff --git a/public/app/features/plugins/admin/components/PluginDetailsSignature.tsx b/public/app/features/plugins/admin/components/PluginDetailsSignature.tsx index 210d71b73b3..bdb0a250e62 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsSignature.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsSignature.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { selectors } from '@grafana/e2e-selectors'; + import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Alert } from '@grafana/ui'; + import { CatalogPlugin } from '../types'; type Props = { diff --git a/public/app/features/plugins/admin/components/PluginList.tsx b/public/app/features/plugins/admin/components/PluginList.tsx index 62e862aa7f2..97e40cbbd96 100644 --- a/public/app/features/plugins/admin/components/PluginList.tsx +++ b/public/app/features/plugins/admin/components/PluginList.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; +import React from 'react'; import { useLocation } from 'react-router-dom'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + import { CatalogPlugin, PluginListDisplayMode } from '../types'; + import { PluginListItem } from './PluginListItem'; interface Props { diff --git a/public/app/features/plugins/admin/components/PluginListItem.test.tsx b/public/app/features/plugins/admin/components/PluginListItem.test.tsx index fb2ed8558b0..856709f4589 100644 --- a/public/app/features/plugins/admin/components/PluginListItem.test.tsx +++ b/public/app/features/plugins/admin/components/PluginListItem.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/data'; -import { PluginListItem } from './PluginListItem'; + import { CatalogPlugin, PluginListDisplayMode } from '../types'; +import { PluginListItem } from './PluginListItem'; + /** * The whole Icon component needs to be mock * currently is using react-inlinesvg that does not render the icon svg in the test. diff --git a/public/app/features/plugins/admin/components/PluginListItem.tsx b/public/app/features/plugins/admin/components/PluginListItem.tsx index 772f5f9a0b8..012f9b0803a 100644 --- a/public/app/features/plugins/admin/components/PluginListItem.tsx +++ b/public/app/features/plugins/admin/components/PluginListItem.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; + import { CatalogPlugin, PluginIconName, PluginListDisplayMode } from '../types'; + import { PluginListItemBadges } from './PluginListItemBadges'; import { PluginLogo } from './PluginLogo'; -import { Icon, useStyles2 } from '@grafana/ui'; export const LOGO_SIZE = '48px'; diff --git a/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx b/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx index b455f9bd42a..e649279fd47 100644 --- a/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx +++ b/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data'; -import { PluginListItemBadges } from './PluginListItemBadges'; -import { CatalogPlugin } from '../types'; import { config } from '@grafana/runtime'; +import { CatalogPlugin } from '../types'; + +import { PluginListItemBadges } from './PluginListItemBadges'; + describe('PluginListItemBadges', () => { const plugin: CatalogPlugin = { description: 'The test plugin', diff --git a/public/app/features/plugins/admin/components/PluginListItemBadges.tsx b/public/app/features/plugins/admin/components/PluginListItemBadges.tsx index e96c9b8431b..bfc21d07130 100644 --- a/public/app/features/plugins/admin/components/PluginListItemBadges.tsx +++ b/public/app/features/plugins/admin/components/PluginListItemBadges.tsx @@ -1,6 +1,9 @@ import React from 'react'; + import { HorizontalGroup, PluginSignatureBadge } from '@grafana/ui'; + import { CatalogPlugin } from '../types'; + import { PluginEnterpriseBadge, PluginDisabledBadge, PluginInstalledBadge, PluginUpdateAvailableBadge } from './Badges'; type PluginBadgeType = { diff --git a/public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx b/public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx index fc87f60b461..158e0ed27b9 100644 --- a/public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx +++ b/public/app/features/plugins/admin/components/PluginSignatureDetailsBadge.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { css } from '@emotion/css'; import { capitalize } from 'lodash'; +import React from 'react'; + import { GrafanaTheme2, PluginSignatureType } from '@grafana/data'; import { useStyles2, Icon, Badge, IconName } from '@grafana/ui'; diff --git a/public/app/features/plugins/admin/components/PluginTypeIcon.tsx b/public/app/features/plugins/admin/components/PluginTypeIcon.tsx index bf4d39e1bee..0d0d9d716f4 100644 --- a/public/app/features/plugins/admin/components/PluginTypeIcon.tsx +++ b/public/app/features/plugins/admin/components/PluginTypeIcon.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { PluginTypeCode } from '../types'; interface PluginTypeIconProps { diff --git a/public/app/features/plugins/admin/components/SearchField.tsx b/public/app/features/plugins/admin/components/SearchField.tsx index e09996e76c0..c251f34b1cc 100644 --- a/public/app/features/plugins/admin/components/SearchField.tsx +++ b/public/app/features/plugins/admin/components/SearchField.tsx @@ -1,7 +1,8 @@ -import { FilterInput } from '@grafana/ui'; import React, { useState, useRef } from 'react'; import { useDebounce } from 'react-use'; +import { FilterInput } from '@grafana/ui'; + interface Props { value?: string; onSearch: (value: string) => void; diff --git a/public/app/features/plugins/admin/components/VersionList.tsx b/public/app/features/plugins/admin/components/VersionList.tsx index 9942a873303..7224bd35065 100644 --- a/public/app/features/plugins/admin/components/VersionList.tsx +++ b/public/app/features/plugins/admin/components/VersionList.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; import { dateTimeFormatTimeAgo, GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { Version } from '../types'; + import { getLatestCompatibleVersion } from '../helpers'; +import { Version } from '../types'; interface Props { versions?: Version[]; diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts index 0c50d50dee5..b88a5d65887 100644 --- a/public/app/features/plugins/admin/helpers.test.ts +++ b/public/app/features/plugins/admin/helpers.test.ts @@ -1,7 +1,7 @@ -import { RemotePlugin, LocalPlugin } from './types'; -import { getLocalPluginMock, getRemotePluginMock, getCatalogPluginMock } from './__mocks__'; import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data'; import { config } from '@grafana/runtime'; + +import { getLocalPluginMock, getRemotePluginMock, getCatalogPluginMock } from './__mocks__'; import { mapToCatalogPlugin, mapRemoteToCatalog, @@ -13,6 +13,7 @@ import { isLocalPluginVisible, isRemotePluginVisible, } from './helpers'; +import { RemotePlugin, LocalPlugin } from './types'; describe('Plugins/Helpers', () => { let remotePlugin: RemotePlugin; diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 7c044a2e3b9..2a52055b3a4 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -1,7 +1,8 @@ -import { config } from '@grafana/runtime'; import { PluginSignatureStatus, dateTimeParse, PluginError, PluginErrorCode } from '@grafana/data'; -import { getBackendSrv } from 'app/core/services/backend_srv'; +import { config } from '@grafana/runtime'; import { Settings } from 'app/core/config'; +import { getBackendSrv } from 'app/core/services/backend_srv'; + import { CatalogPlugin, LocalPlugin, RemotePlugin, Version } from './types'; export function mergeLocalsAndRemotes( diff --git a/public/app/features/plugins/admin/hooks/usePluginConfig.tsx b/public/app/features/plugins/admin/hooks/usePluginConfig.tsx index 43a2fe47809..d515ba81594 100644 --- a/public/app/features/plugins/admin/hooks/usePluginConfig.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginConfig.tsx @@ -1,6 +1,7 @@ import { useAsync } from 'react-use'; -import { CatalogPlugin } from '../types'; + import { loadPlugin } from '../../utils'; +import { CatalogPlugin } from '../types'; export const usePluginConfig = (plugin?: CatalogPlugin) => { return useAsync(async () => { diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx index 6a045fca1a4..40c17a061f7 100644 --- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx @@ -1,9 +1,11 @@ import { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; + import { PluginIncludeType, PluginType } from '@grafana/data'; -import { CatalogPlugin, PluginDetailsTab, PluginTabIds, PluginTabLabels } from '../types'; + import { usePluginConfig } from '../hooks/usePluginConfig'; import { isOrgAdmin } from '../permissions'; +import { CatalogPlugin, PluginDetailsTab, PluginTabIds, PluginTabLabels } from '../types'; type ReturnType = { error: Error | undefined; diff --git a/public/app/features/plugins/admin/pages/Browse.test.tsx b/public/app/features/plugins/admin/pages/Browse.test.tsx index 0e9cbc4d86a..56096458df0 100644 --- a/public/app/features/plugins/admin/pages/Browse.test.tsx +++ b/public/app/features/plugins/admin/pages/Browse.test.tsx @@ -1,15 +1,18 @@ -import React from 'react'; -import { Router } from 'react-router-dom'; import { render, RenderResult, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Provider } from 'react-redux'; -import { locationService } from '@grafana/runtime'; +import { Router } from 'react-router-dom'; + import { PluginType } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; import { configureStore } from 'app/store/configureStore'; + +import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__'; import { fetchRemotePlugins } from '../state/actions'; import { PluginAdminRoutes, CatalogPlugin, ReducerState, RequestStatus } from '../types'; -import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__'; + import BrowsePage from './Browse'; jest.mock('@grafana/runtime', () => { @@ -349,7 +352,7 @@ describe('Browse list of plugins', () => { expect(listOption).not.toBeChecked(); // Switch to "list" view - userEvent.click(listOption); + await userEvent.click(listOption); expect(gridOption).not.toBeChecked(); expect(listOption).toBeChecked(); diff --git a/public/app/features/plugins/admin/pages/Browse.tsx b/public/app/features/plugins/admin/pages/Browse.tsx index 2d8a08eafee..91d5ae6d2e9 100644 --- a/public/app/features/plugins/admin/pages/Browse.tsx +++ b/public/app/features/plugins/admin/pages/Browse.tsx @@ -1,22 +1,24 @@ -import React, { ReactElement } from 'react'; import { css } from '@emotion/css'; -import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; -import { LoadingPlaceholder, Select, RadioButtonGroup, useStyles2, Tooltip } from '@grafana/ui'; +import React, { ReactElement } from 'react'; +import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; + +import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; import { locationSearchToObject } from '@grafana/runtime'; +import { LoadingPlaceholder, Select, RadioButtonGroup, useStyles2, Tooltip } from '@grafana/ui'; +import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types/store'; + +import { HorizontalGroup } from '../components/HorizontalGroup'; +import { Page as PluginPage } from '../components/Page'; import { PluginList } from '../components/PluginList'; import { SearchField } from '../components/SearchField'; -import { useHistory } from '../hooks/useHistory'; -import { PluginAdminRoutes, PluginListDisplayMode } from '../types'; -import { Page as PluginPage } from '../components/Page'; -import { HorizontalGroup } from '../components/HorizontalGroup'; -import { Page } from 'app/core/components/Page/Page'; -import { useSelector } from 'react-redux'; -import { StoreState } from 'app/types/store'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { useGetAllWithFilters, useIsRemotePluginsAvailable, useDisplayMode } from '../state/hooks'; import { Sorters } from '../helpers'; +import { useHistory } from '../hooks/useHistory'; +import { useGetAllWithFilters, useIsRemotePluginsAvailable, useDisplayMode } from '../state/hooks'; +import { PluginAdminRoutes, PluginListDisplayMode } from '../types'; export default function Browse({ route }: GrafanaRouteComponentProps): ReactElement | null { const location = useLocation(); diff --git a/public/app/features/plugins/admin/pages/NotEnabed.tsx b/public/app/features/plugins/admin/pages/NotEnabed.tsx index e0442d31c39..9d56dab9319 100644 --- a/public/app/features/plugins/admin/pages/NotEnabed.tsx +++ b/public/app/features/plugins/admin/pages/NotEnabed.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { NavModel, NavModelItem } from '@grafana/data'; -import { Page as PluginPage } from '../components/Page'; import { Page } from 'app/core/components/Page/Page'; +import { Page as PluginPage } from '../components/Page'; + const node: NavModelItem = { id: 'not-found', text: 'The plugin catalog is not enabled', diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index 8d0e8d367f0..1a89c61e3e7 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -1,13 +1,19 @@ +import { getDefaultNormalizer, render, RenderResult, SelectorMatcherOptions, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; -import { getDefaultNormalizer, render, RenderResult, SelectorMatcherOptions, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; + +import { PluginErrorCode, PluginSignatureStatus, PluginType, dateTimeFormatTimeAgo } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; -import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock, mockUserPermissions } from '../__mocks__'; -import { configureStore } from 'app/store/configureStore'; -import PluginDetailsPage from './PluginDetails'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { configureStore } from 'app/store/configureStore'; + +import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock, mockUserPermissions } from '../__mocks__'; +import * as api from '../api'; +import { usePluginConfig } from '../hooks/usePluginConfig'; +import { fetchRemotePlugins } from '../state/actions'; import { CatalogPlugin, CatalogPluginDetails, @@ -16,11 +22,8 @@ import { ReducerState, RequestStatus, } from '../types'; -import * as api from '../api'; -import { fetchRemotePlugins } from '../state/actions'; -import { usePluginConfig } from '../hooks/usePluginConfig'; -import { PluginErrorCode, PluginSignatureStatus, PluginType, dateTimeFormatTimeAgo } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; + +import PluginDetailsPage from './PluginDetails'; jest.mock('@grafana/runtime', () => { const original = jest.requireActual('@grafana/runtime'); @@ -447,14 +450,14 @@ describe('Plugin details page', () => { await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); // Open the confirmation modal - userEvent.click(getByRole('button', { name: /uninstall/i })); + await userEvent.click(getByRole('button', { name: /uninstall/i })); expect(queryByText('Uninstall Akumuli')).toBeInTheDocument(); expect(queryByText('Are you sure you want to uninstall this plugin?')).toBeInTheDocument(); expect(api.uninstallPlugin).toHaveBeenCalledTimes(0); // Confirm the uninstall - userEvent.click(getByRole('button', { name: /confirm/i })); + await userEvent.click(getByRole('button', { name: /confirm/i })); expect(api.uninstallPlugin).toHaveBeenCalledTimes(1); expect(api.uninstallPlugin).toHaveBeenCalledWith(id); @@ -635,7 +638,7 @@ describe('Plugin details page', () => { await waitFor(() => queryByText('Uninstall')); // Click on "Enable" - userEvent.click(getByRole('button', { name: /enable/i })); + await userEvent.click(getByRole('button', { name: /enable/i })); // Check if the API request was initiated expect(api.updatePluginSettings).toHaveBeenCalledTimes(1); @@ -675,7 +678,7 @@ describe('Plugin details page', () => { await waitFor(() => queryByText('Uninstall')); // Click on "Disable" - userEvent.click(getByRole('button', { name: /disable/i })); + await userEvent.click(getByRole('button', { name: /disable/i })); // Check if the API request was initiated expect(api.updatePluginSettings).toHaveBeenCalledTimes(1); diff --git a/public/app/features/plugins/admin/pages/PluginDetails.tsx b/public/app/features/plugins/admin/pages/PluginDetails.tsx index 8674ca21f0d..394bdfbe8b1 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.tsx @@ -1,22 +1,24 @@ -import React, { useEffect } from 'react'; import { css } from '@emotion/css'; +import React, { useEffect } from 'react'; import { usePrevious } from 'react-use'; + import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2, TabsBar, TabContent, Tab, Alert, IconName } from '@grafana/ui'; import { locationService } from '@grafana/runtime'; +import { useStyles2, TabsBar, TabContent, Tab, Alert, IconName } from '@grafana/ui'; import { Layout } from '@grafana/ui/src/components/Layout/Layout'; import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; -import { PluginDetailsHeader } from '../components/PluginDetailsHeader'; -import { PluginDetailsBody } from '../components/PluginDetailsBody'; -import { Page as PluginPage } from '../components/Page'; -import { Loader } from '../components/Loader'; -import { PluginTabLabels, PluginTabIds, PluginDetailsTab } from '../types'; -import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks'; -import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; import { AppNotificationSeverity } from 'app/types'; + +import { Loader } from '../components/Loader'; +import { Page as PluginPage } from '../components/Page'; +import { PluginDetailsBody } from '../components/PluginDetailsBody'; import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError'; +import { PluginDetailsHeader } from '../components/PluginDetailsHeader'; +import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; +import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; +import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks'; +import { PluginTabLabels, PluginTabIds, PluginDetailsTab } from '../types'; type Props = GrafanaRouteComponentProps<{ pluginId?: string }>; diff --git a/public/app/features/plugins/admin/routes.ts b/public/app/features/plugins/admin/routes.ts index f2f9f6317ca..02a54c1c6c8 100644 --- a/public/app/features/plugins/admin/routes.ts +++ b/public/app/features/plugins/admin/routes.ts @@ -1,5 +1,6 @@ import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; import { RouteDescriptor } from 'app/core/navigation/types'; + import { isGrafanaAdmin } from './permissions'; import { PluginAdminRoutes } from './types'; diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index e23bf4b0b0e..408f1d38833 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -1,8 +1,11 @@ import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit'; -import { getBackendSrv } from '@grafana/runtime'; + import { PanelPlugin } from '@grafana/data'; -import { StoreState, ThunkResult } from 'app/types'; +import { getBackendSrv } from '@grafana/runtime'; import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin'; +import { StoreState, ThunkResult } from 'app/types'; + +import { invalidatePluginInCache } from '../../pluginCacheBuster'; import { getRemotePlugins, getPluginErrors, @@ -14,7 +17,6 @@ import { import { STATE_PREFIX } from '../constants'; import { mergeLocalsAndRemotes, updatePanels } from '../helpers'; import { CatalogPlugin, RemotePlugin } from '../types'; -import { invalidatePluginInCache } from '../../pluginCacheBuster'; export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => { try { diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts index e6eaae31789..9a4862f3d19 100644 --- a/public/app/features/plugins/admin/state/hooks.ts +++ b/public/app/features/plugins/admin/state/hooks.ts @@ -1,9 +1,13 @@ import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; + import { PluginError } from '@grafana/data'; -import { setDisplayMode } from './reducer'; -import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall } from './actions'; + +import { sortPlugins, Sorters } from '../helpers'; import { CatalogPlugin, PluginCatalogStoreState, PluginListDisplayMode } from '../types'; + +import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall } from './actions'; +import { setDisplayMode } from './reducer'; import { find, selectAll, @@ -14,7 +18,6 @@ import { selectDisplayMode, selectPluginErrors, } from './selectors'; -import { sortPlugins, Sorters } from '../helpers'; type Filters = { query?: string; diff --git a/public/app/features/plugins/admin/state/reducer.ts b/public/app/features/plugins/admin/state/reducer.ts index 565b84973f8..515e9536fdb 100644 --- a/public/app/features/plugins/admin/state/reducer.ts +++ b/public/app/features/plugins/admin/state/reducer.ts @@ -1,9 +1,12 @@ import { createSlice, createEntityAdapter, Reducer, AnyAction, PayloadAction } from '@reduxjs/toolkit'; -import { fetchAll, fetchDetails, install, uninstall, loadPluginDashboards, panelPluginLoaded } from './actions'; -import { CatalogPlugin, PluginListDisplayMode, ReducerState, RequestStatus } from '../types'; -import { STATE_PREFIX } from '../constants'; + import { PanelPlugin } from '@grafana/data'; +import { STATE_PREFIX } from '../constants'; +import { CatalogPlugin, PluginListDisplayMode, ReducerState, RequestStatus } from '../types'; + +import { fetchAll, fetchDetails, install, uninstall, loadPluginDashboards, panelPluginLoaded } from './actions'; + export const pluginsAdapter = createEntityAdapter(); const isPendingRequest = (action: AnyAction) => new RegExp(`${STATE_PREFIX}\/(.*)\/pending`).test(action.type); diff --git a/public/app/features/plugins/admin/state/selectors.ts b/public/app/features/plugins/admin/state/selectors.ts index a0dae21aba9..b8bced29eae 100644 --- a/public/app/features/plugins/admin/state/selectors.ts +++ b/public/app/features/plugins/admin/state/selectors.ts @@ -1,6 +1,9 @@ import { createSelector } from '@reduxjs/toolkit'; + import { PluginError, PluginErrorCode } from '@grafana/data'; + import { RequestStatus, PluginCatalogStoreState } from '../types'; + import { pluginsAdapter } from './reducer'; export const selectRoot = (state: PluginCatalogStoreState) => state.plugins; diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index a20e212b427..53907d3bdb6 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -1,4 +1,5 @@ import { EntityState } from '@reduxjs/toolkit'; + import { PluginType, PluginSignatureStatus, diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts index 3128e26f815..9bd37e1a13f 100644 --- a/public/app/features/plugins/built_in_plugins.ts +++ b/public/app/features/plugins/built_in_plugins.ts @@ -40,31 +40,31 @@ const tempoPlugin = async () => const alertmanagerPlugin = async () => await import(/* webpackChunkName: "alertmanagerPlugin" */ 'app/plugins/datasource/alertmanager/module'); -import * as textPanel from 'app/plugins/panel/text/module'; -import * as timeseriesPanel from 'app/plugins/panel/timeseries/module'; -import * as stateTimelinePanel from 'app/plugins/panel/state-timeline/module'; -import * as statusHistoryPanel from 'app/plugins/panel/status-history/module'; -import * as candlestickPanel from 'app/plugins/panel/candlestick/module'; -import * as xyChartPanel from 'app/plugins/panel/xychart/module'; -import * as dashListPanel from 'app/plugins/panel/dashlist/module'; +import * as alertGroupsPanel from 'app/plugins/panel/alertGroups/module'; import * as alertListPanel from 'app/plugins/panel/alertlist/module'; import * as annoListPanel from 'app/plugins/panel/annolist/module'; -import * as heatmapPanelNG from 'app/plugins/panel/heatmap-new/module'; -import * as tablePanel from 'app/plugins/panel/table/module'; -import * as statPanel from 'app/plugins/panel/stat/module'; -import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module'; -import * as gaugePanel from 'app/plugins/panel/gauge/module'; -import * as pieChartPanel from 'app/plugins/panel/piechart/module'; -import * as barGaugePanel from 'app/plugins/panel/bargauge/module'; import * as barChartPanel from 'app/plugins/panel/barchart/module'; +import * as barGaugePanel from 'app/plugins/panel/bargauge/module'; +import * as candlestickPanel from 'app/plugins/panel/candlestick/module'; +import * as dashListPanel from 'app/plugins/panel/dashlist/module'; +import * as debugPanel from 'app/plugins/panel/debug/module'; +import * as gaugePanel from 'app/plugins/panel/gauge/module'; +import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module'; +import * as heatmapPanelNG from 'app/plugins/panel/heatmap-new/module'; +import * as histogramPanel from 'app/plugins/panel/histogram/module'; +import * as livePanel from 'app/plugins/panel/live/module'; import * as logsPanel from 'app/plugins/panel/logs/module'; import * as newsPanel from 'app/plugins/panel/news/module'; -import * as livePanel from 'app/plugins/panel/live/module'; -import * as debugPanel from 'app/plugins/panel/debug/module'; -import * as welcomeBanner from 'app/plugins/panel/welcome/module'; import * as nodeGraph from 'app/plugins/panel/nodeGraph/module'; -import * as histogramPanel from 'app/plugins/panel/histogram/module'; -import * as alertGroupsPanel from 'app/plugins/panel/alertGroups/module'; +import * as pieChartPanel from 'app/plugins/panel/piechart/module'; +import * as statPanel from 'app/plugins/panel/stat/module'; +import * as stateTimelinePanel from 'app/plugins/panel/state-timeline/module'; +import * as statusHistoryPanel from 'app/plugins/panel/status-history/module'; +import * as tablePanel from 'app/plugins/panel/table/module'; +import * as textPanel from 'app/plugins/panel/text/module'; +import * as timeseriesPanel from 'app/plugins/panel/timeseries/module'; +import * as welcomeBanner from 'app/plugins/panel/welcome/module'; +import * as xyChartPanel from 'app/plugins/panel/xychart/module'; // Async loaded panels const geomapPanel = async () => await import(/* webpackChunkName: "geomapPanel" */ 'app/plugins/panel/geomap/module'); diff --git a/public/app/features/plugins/components/AppRootPage.test.tsx b/public/app/features/plugins/components/AppRootPage.test.tsx index cf6609f3c3d..cf9ba1f02ed 100644 --- a/public/app/features/plugins/components/AppRootPage.test.tsx +++ b/public/app/features/plugins/components/AppRootPage.test.tsx @@ -1,15 +1,18 @@ import { act, render, screen } from '@testing-library/react'; import React, { Component } from 'react'; -import AppRootPage from './AppRootPage'; -import { getPluginSettings } from '../pluginSettings'; -import { importAppPlugin } from '../plugin_loader'; -import { getMockPlugin } from '../__mocks__/pluginMocks'; -import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data'; import { Route, Router } from 'react-router-dom'; + +import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data'; import { locationService, setEchoSrv } from '@grafana/runtime'; import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute'; import { Echo } from 'app/core/services/echo/Echo'; +import { getMockPlugin } from '../__mocks__/pluginMocks'; +import { getPluginSettings } from '../pluginSettings'; +import { importAppPlugin } from '../plugin_loader'; + +import AppRootPage from './AppRootPage'; + jest.mock('../pluginSettings', () => ({ getPluginSettings: jest.fn(), })); diff --git a/public/app/features/plugins/components/AppRootPage.tsx b/public/app/features/plugins/components/AppRootPage.tsx index faa63014b8f..c5b7b6c20a1 100644 --- a/public/app/features/plugins/components/AppRootPage.tsx +++ b/public/app/features/plugins/components/AppRootPage.tsx @@ -1,15 +1,16 @@ // Libraries import React, { Component } from 'react'; -import { AppEvents, AppPlugin, AppPluginMeta, KeyValue, NavModel, PluginType } from '@grafana/data'; import { createHtmlPortalNode, InPortal, OutPortal, HtmlPortalNode } from 'react-reverse-portal'; +import { AppEvents, AppPlugin, AppPluginMeta, KeyValue, NavModel, PluginType } from '@grafana/data'; +import { getNotFoundNav, getWarningNav, getExceptionNav } from 'app/angular/services/nav_model_srv'; import Page from 'app/core/components/Page/Page'; +import PageLoader from 'app/core/components/PageLoader/PageLoader'; +import { appEvents } from 'app/core/core'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; + import { getPluginSettings } from '../pluginSettings'; import { importAppPlugin } from '../plugin_loader'; -import { getNotFoundNav, getWarningNav, getExceptionNav } from 'app/angular/services/nav_model_srv'; -import { appEvents } from 'app/core/core'; -import PageLoader from 'app/core/components/PageLoader/PageLoader'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; interface RouteParams { pluginId: string; } diff --git a/public/app/features/plugins/components/PluginStateInfo.tsx b/public/app/features/plugins/components/PluginStateInfo.tsx index 2552cf9f997..066034de2f8 100644 --- a/public/app/features/plugins/components/PluginStateInfo.tsx +++ b/public/app/features/plugins/components/PluginStateInfo.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; -import { Badge, BadgeProps } from '@grafana/ui'; + import { PluginState } from '@grafana/data'; +import { Badge, BadgeProps } from '@grafana/ui'; interface Props { state?: PluginState; diff --git a/public/app/features/plugins/components/PluginsErrorsInfo.tsx b/public/app/features/plugins/components/PluginsErrorsInfo.tsx index 6be74a424e1..9d085ee0fcf 100644 --- a/public/app/features/plugins/components/PluginsErrorsInfo.tsx +++ b/public/app/features/plugins/components/PluginsErrorsInfo.tsx @@ -1,9 +1,11 @@ +import { css } from '@emotion/css'; import React from 'react'; + +import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { HorizontalGroup, InfoBox, List, PluginSignatureBadge, useTheme } from '@grafana/ui'; + import { useGetErrors, useFetchStatus } from '../admin/state/hooks'; -import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data'; -import { css } from '@emotion/css'; export function PluginsErrorsInfo(): React.ReactElement | null { const errors = useGetErrors(); diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index a78301cc87d..6eb54233043 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -1,5 +1,11 @@ -// Services & Utils -import { importDataSourcePlugin } from './plugin_loader'; +import { + AppEvents, + DataSourceApi, + DataSourceInstanceSettings, + DataSourceRef, + DataSourceSelectItem, + ScopedVars, +} from '@grafana/data'; import { GetDataSourceListFilters, DataSourceSrv as DataSourceService, @@ -9,25 +15,18 @@ import { getLegacyAngularInjector, getBackendSrv, } from '@grafana/runtime'; -// Types -import { - AppEvents, - DataSourceApi, - DataSourceInstanceSettings, - DataSourceRef, - DataSourceSelectItem, - ScopedVars, -} from '@grafana/data'; -// Pretend Datasource +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import appEvents from 'app/core/app_events'; +import config from 'app/core/config'; import { dataSource as expressionDatasource, ExpressionDatasourceUID, instanceSettings as expressionInstanceSettings, } from 'app/features/expressions/ExpressionDatasource'; + import { DataSourceVariableModel } from '../variables/types'; -import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; -import appEvents from 'app/core/app_events'; -import config from 'app/core/config'; + +import { importDataSourcePlugin } from './plugin_loader'; export class DatasourceSrv implements DataSourceService { private datasources: Record = {}; // UID diff --git a/public/app/features/plugins/importPanelPlugin.ts b/public/app/features/plugins/importPanelPlugin.ts index ccb3bdf9251..042db64bae1 100644 --- a/public/app/features/plugins/importPanelPlugin.ts +++ b/public/app/features/plugins/importPanelPlugin.ts @@ -1,6 +1,8 @@ -import config from 'app/core/config'; import * as grafanaData from '@grafana/data'; +import config from 'app/core/config'; + import { getPanelPluginLoadError } from '../panel/components/PanelPluginError'; + import { importPluginModule } from './plugin_loader'; interface PanelCache { [key: string]: Promise; diff --git a/public/app/features/plugins/pluginPreloader.ts b/public/app/features/plugins/pluginPreloader.ts index 34c10c44097..a8703471d89 100644 --- a/public/app/features/plugins/pluginPreloader.ts +++ b/public/app/features/plugins/pluginPreloader.ts @@ -1,4 +1,5 @@ import { PreloadPlugin } from '@grafana/data'; + import { importPluginModule } from './plugin_loader'; export async function preloadPlugins(pluginsToPreload: PreloadPlugin[] = []): Promise { diff --git a/public/app/features/plugins/pluginSettings.test.ts b/public/app/features/plugins/pluginSettings.test.ts index 08d6b67a5fc..c987f430180 100644 --- a/public/app/features/plugins/pluginSettings.test.ts +++ b/public/app/features/plugins/pluginSettings.test.ts @@ -1,6 +1,7 @@ -import { getPluginSettings, clearPluginSettingsCache } from './pluginSettings'; import { getBackendSrv } from '@grafana/runtime'; +import { getPluginSettings, clearPluginSettingsCache } from './pluginSettings'; + jest.mock('@grafana/runtime', () => ({ getBackendSrv: jest.fn().mockReturnValue({ get: jest.fn(), diff --git a/public/app/features/plugins/pluginSettings.ts b/public/app/features/plugins/pluginSettings.ts index 29a9f041cef..d3f21b83a8c 100644 --- a/public/app/features/plugins/pluginSettings.ts +++ b/public/app/features/plugins/pluginSettings.ts @@ -1,5 +1,5 @@ -import { getBackendSrv } from '@grafana/runtime'; import { PluginMeta } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime'; type PluginCache = { [key: string]: PluginMeta; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 831a5947762..d3685cab5d2 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -1,36 +1,36 @@ -// eslint-disable-next-line lodash/import-scope -import _ from 'lodash'; -import kbn from 'app/core/utils/kbn'; -import moment from 'moment'; // eslint-disable-line no-restricted-imports +import * as emotion from '@emotion/css'; +import * as d3 from 'd3'; import jquery from 'jquery'; - -// Experimental module exports +import _ from 'lodash'; // eslint-disable-line lodash/import-scope +import moment from 'moment'; // eslint-disable-line no-restricted-imports import prismjs from 'prismjs'; -import slate from 'slate'; -// @ts-ignore -import slateReact from '@grafana/slate-react'; -// @ts-ignore -import slatePlain from 'slate-plain-serializer'; import react from 'react'; import reactDom from 'react-dom'; import * as reactRedux from 'react-redux'; +import * as reactRouter from 'react-router-dom'; import * as redux from 'redux'; +import * as rxjs from 'rxjs'; +import * as rxjsOperators from 'rxjs/operators'; +import slate from 'slate'; +import slatePlain from 'slate-plain-serializer'; +import * as grafanaData from '@grafana/data'; +import * as grafanaRuntime from '@grafana/runtime'; +import slateReact from '@grafana/slate-react'; +import * as grafanaUIraw from '@grafana/ui'; import config from 'app/core/config'; -import TimeSeries from 'app/core/time_series2'; -import TableModel from 'app/core/table_model'; import { appEvents, contextSrv } from 'app/core/core'; -import * as flatten from 'app/core/utils/flatten'; -import * as ticks from 'app/core/utils/ticks'; import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; import impressionSrv from 'app/core/services/impression_srv'; -import builtInPlugins from './built_in_plugins'; -import * as d3 from 'd3'; -import * as emotion from '@emotion/css'; -import * as grafanaData from '@grafana/data'; -import * as grafanaUIraw from '@grafana/ui'; -import * as grafanaRuntime from '@grafana/runtime'; +import TableModel from 'app/core/table_model'; +import TimeSeries from 'app/core/time_series2'; +import * as flatten from 'app/core/utils/flatten'; +import kbn from 'app/core/utils/kbn'; +import * as ticks from 'app/core/utils/ticks'; + import { GenericDataSourcePlugin } from '../datasources/settings/PluginSettings'; + +import builtInPlugins from './built_in_plugins'; import { locateWithCache, registerPluginInCache } from './pluginCacheBuster'; // Help the 6.4 to 6.5 migration @@ -42,12 +42,6 @@ grafanaUI.DataSourcePlugin = grafanaData.DataSourcePlugin; grafanaUI.AppPlugin = grafanaData.AppPlugin; grafanaUI.DataSourceApi = grafanaData.DataSourceApi; -// rxjs -import * as rxjs from 'rxjs'; -import * as rxjsOperators from 'rxjs/operators'; -// routing -import * as reactRouter from 'react-router-dom'; - grafanaRuntime.SystemJS.registry.set('plugin-loader', grafanaRuntime.SystemJS.newModule({ locate: locateWithCache })); grafanaRuntime.SystemJS.config({ diff --git a/public/app/features/plugins/tests/datasource_srv.test.ts b/public/app/features/plugins/tests/datasource_srv.test.ts index eba0d0dc8ce..14517b7d3e8 100644 --- a/public/app/features/plugins/tests/datasource_srv.test.ts +++ b/public/app/features/plugins/tests/datasource_srv.test.ts @@ -1,4 +1,3 @@ -import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DataSourceApi, DataSourceInstanceSettings, @@ -6,6 +5,7 @@ import { DataSourcePluginMeta, ScopedVar, } from '@grafana/data'; +import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; // Datasource variable $datasource with current value 'BBB' const templateSrv: any = { diff --git a/public/app/features/plugins/tests/plugin_loader.test.ts b/public/app/features/plugins/tests/plugin_loader.test.ts index 2ddc26bd51f..e7af4f5d919 100644 --- a/public/app/features/plugins/tests/plugin_loader.test.ts +++ b/public/app/features/plugins/tests/plugin_loader.test.ts @@ -13,8 +13,8 @@ jest.mock('app/core/core', () => { }; }); -import { SystemJS } from '@grafana/runtime'; import { AppPluginMeta, PluginMetaInfo, PluginType, AppPlugin } from '@grafana/data'; +import { SystemJS } from '@grafana/runtime'; // Loaded after the `unmock` abve import { importAppPlugin } from '../plugin_loader'; diff --git a/public/app/features/plugins/utils.ts b/public/app/features/plugins/utils.ts index d11ef5150d7..f1662fa7b7a 100644 --- a/public/app/features/plugins/utils.ts +++ b/public/app/features/plugins/utils.ts @@ -1,7 +1,8 @@ import { GrafanaPlugin, PanelPluginMeta, PluginType } from '@grafana/data'; + +import { importPanelPluginFromMeta } from './importPanelPlugin'; import { getPluginSettings } from './pluginSettings'; import { importAppPlugin, importDataSourcePlugin } from './plugin_loader'; -import { importPanelPluginFromMeta } from './importPanelPlugin'; export async function loadPlugin(pluginId: string): Promise { const info = await getPluginSettings(pluginId); diff --git a/public/app/features/profile/ChangePasswordForm.tsx b/public/app/features/profile/ChangePasswordForm.tsx index 05b6be12a2b..d86dcd1e8e9 100644 --- a/public/app/features/profile/ChangePasswordForm.tsx +++ b/public/app/features/profile/ChangePasswordForm.tsx @@ -1,12 +1,14 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; -import { Button, Field, Form, HorizontalGroup, LinkButton } from '@grafana/ui'; +import React, { FC } from 'react'; +import { Button, Field, Form, HorizontalGroup, LinkButton } from '@grafana/ui'; import config from 'app/core/config'; import { UserDTO } from 'app/types'; -import { ChangePasswordFields } from './types'; + import { PasswordField } from '../../core/components/PasswordField/PasswordField'; +import { ChangePasswordFields } from './types'; + export interface Props { user: UserDTO; isSaving: boolean; diff --git a/public/app/features/profile/ChangePasswordPage.test.tsx b/public/app/features/profile/ChangePasswordPage.test.tsx index eebd890fa11..2c6e15e39d9 100644 --- a/public/app/features/profile/ChangePasswordPage.test.tsx +++ b/public/app/features/profile/ChangePasswordPage.test.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import config from 'app/core/config'; -import { Props, ChangePasswordPage } from './ChangePasswordPage'; -import { initialUserState } from './state/reducers'; + import { getNavModel } from '../../core/selectors/navModel'; import { backendSrv } from '../../core/services/backend_srv'; +import { Props, ChangePasswordPage } from './ChangePasswordPage'; +import { initialUserState } from './state/reducers'; + const defaultProps: Props = { ...initialUserState, user: { @@ -83,9 +86,9 @@ describe('ChangePasswordPage', () => { it('should call changePassword if change password is valid', async () => { const { props } = await getTestContext(); - userEvent.type(screen.getByLabelText('Old password'), 'test'); - userEvent.type(screen.getByLabelText('New password'), 'admin'); - userEvent.type(screen.getByLabelText('Confirm password'), 'admin'); + await userEvent.type(screen.getByLabelText('Old password'), 'test'); + await userEvent.type(screen.getByLabelText('New password'), 'admin'); + await userEvent.type(screen.getByLabelText('Confirm password'), 'admin'); fireEvent.click(screen.getByRole('button', { name: 'Change Password' })); await waitFor(() => { expect(props.changePassword).toHaveBeenCalledTimes(1); diff --git a/public/app/features/profile/ChangePasswordPage.tsx b/public/app/features/profile/ChangePasswordPage.tsx index db8ce8f8531..a5324bc1dbc 100644 --- a/public/app/features/profile/ChangePasswordPage.tsx +++ b/public/app/features/profile/ChangePasswordPage.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { useMount } from 'react-use'; import { connect, ConnectedProps } from 'react-redux'; -import { NavModel } from '@grafana/data'; +import { useMount } from 'react-use'; -import { StoreState } from 'app/types'; -import { getNavModel } from 'app/core/selectors/navModel'; +import { NavModel } from '@grafana/data'; import Page from 'app/core/components/Page/Page'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState } from 'app/types'; + import { ChangePasswordForm } from './ChangePasswordForm'; import { changePassword, loadUser } from './state/actions'; diff --git a/public/app/features/profile/FeatureTogglePage.tsx b/public/app/features/profile/FeatureTogglePage.tsx index 1b7b3be5a2e..6fd197166dd 100644 --- a/public/app/features/profile/FeatureTogglePage.tsx +++ b/public/app/features/profile/FeatureTogglePage.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import Page from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; diff --git a/public/app/features/profile/UserOrganizations.tsx b/public/app/features/profile/UserOrganizations.tsx index 288ec250492..f050c2b51b5 100644 --- a/public/app/features/profile/UserOrganizations.tsx +++ b/public/app/features/profile/UserOrganizations.tsx @@ -1,8 +1,9 @@ -import React, { PureComponent } from 'react'; -import { UserDTO, UserOrg } from 'app/types'; -import { Button, LoadingPlaceholder } from '@grafana/ui'; import { Trans } from '@lingui/macro'; +import React, { PureComponent } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; +import { Button, LoadingPlaceholder } from '@grafana/ui'; +import { UserDTO, UserOrg } from 'app/types'; export interface Props { user: UserDTO | null; diff --git a/public/app/features/profile/UserProfileEditForm.tsx b/public/app/features/profile/UserProfileEditForm.tsx index b1be3a0cf87..f3c8927fbe9 100644 --- a/public/app/features/profile/UserProfileEditForm.tsx +++ b/public/app/features/profile/UserProfileEditForm.tsx @@ -1,9 +1,11 @@ -import React, { FC } from 'react'; import { Trans, t } from '@lingui/macro'; -import { Button, Field, FieldSet, Form, Icon, Input, Tooltip } from '@grafana/ui'; +import React, { FC } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; -import { UserDTO } from 'app/types'; +import { Button, Field, FieldSet, Form, Icon, Input, Tooltip } from '@grafana/ui'; import config from 'app/core/config'; +import { UserDTO } from 'app/types'; + import { ProfileUpdateFields } from './types'; export interface Props { diff --git a/public/app/features/profile/UserProfileEditPage.test.tsx b/public/app/features/profile/UserProfileEditPage.test.tsx index b8f3c476de6..83ada3e7937 100644 --- a/public/app/features/profile/UserProfileEditPage.test.tsx +++ b/public/app/features/profile/UserProfileEditPage.test.tsx @@ -1,17 +1,19 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { within } from '@testing-library/dom'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; +import React from 'react'; + import { OrgRole } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import TestProvider from '../../../test/helpers/TestProvider'; -import { Props, UserProfileEditPage } from './UserProfileEditPage'; -import { initialUserState } from './state/reducers'; +import TestProvider from '../../../test/helpers/TestProvider'; import { getNavModel } from '../../core/selectors/navModel'; import { backendSrv } from '../../core/services/backend_srv'; import { TeamPermissionLevel } from '../../types'; +import { Props, UserProfileEditPage } from './UserProfileEditPage'; +import { initialUserState } from './state/reducers'; + const defaultProps: Props = { ...initialUserState, user: { @@ -239,10 +241,10 @@ describe('UserProfileEditPage', () => { const { props } = await getTestContext(); const { email, saveProfile } = getSelectors(); - userEvent.clear(email()); - userEvent.type(email(), 'test@test.se'); + await userEvent.clear(email()); + await userEvent.type(email(), 'test@test.se'); // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - userEvent.click(saveProfile(), undefined, { skipPointerEventsCheck: true }); + await userEvent.click(saveProfile(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); await waitFor(() => expect(props.updateUserProfile).toHaveBeenCalledTimes(1)); expect(props.updateUserProfile).toHaveBeenCalledWith({ @@ -261,7 +263,7 @@ describe('UserProfileEditPage', () => { name: /select organisation/i, }); - userEvent.click(orgsAdminSelectButton()); + await userEvent.click(orgsAdminSelectButton()); await waitFor(() => expect(props.changeUserOrg).toHaveBeenCalledTimes(1)); expect(props.changeUserOrg).toHaveBeenCalledWith({ @@ -280,7 +282,7 @@ describe('UserProfileEditPage', () => { name: /revoke user session/i, }); - userEvent.click(sessionsRevokeButton()); + await userEvent.click(sessionsRevokeButton()); await waitFor(() => expect(props.revokeUserSession).toHaveBeenCalledTimes(1)); expect(props.revokeUserSession).toHaveBeenCalledWith(0); diff --git a/public/app/features/profile/UserProfileEditPage.tsx b/public/app/features/profile/UserProfileEditPage.tsx index eee60196dd8..008893323b0 100644 --- a/public/app/features/profile/UserProfileEditPage.tsx +++ b/public/app/features/profile/UserProfileEditPage.tsx @@ -1,18 +1,19 @@ import React from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useMount } from 'react-use'; + import { NavModel } from '@grafana/data'; import { VerticalGroup } from '@grafana/ui'; - +import Page from 'app/core/components/Page/Page'; +import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; -import Page from 'app/core/components/Page/Page'; -import { changeUserOrg, initUserProfilePage, revokeUserSession, updateUserProfile } from './state/actions'; -import UserProfileEditForm from './UserProfileEditForm'; -import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences'; -import { UserTeams } from './UserTeams'; + import UserOrganizations from './UserOrganizations'; +import UserProfileEditForm from './UserProfileEditForm'; import UserSessions from './UserSessions'; +import { UserTeams } from './UserTeams'; +import { changeUserOrg, initUserProfilePage, revokeUserSession, updateUserProfile } from './state/actions'; export interface OwnProps { navModel: NavModel; diff --git a/public/app/features/profile/UserSessions.tsx b/public/app/features/profile/UserSessions.tsx index 5036b501450..5a10f7e769e 100644 --- a/public/app/features/profile/UserSessions.tsx +++ b/public/app/features/profile/UserSessions.tsx @@ -1,9 +1,10 @@ -import React, { PureComponent } from 'react'; -import { UserSession } from 'app/types'; -import { Button, Icon, LoadingPlaceholder } from '@grafana/ui'; -import { withI18n, withI18nProps } from '@lingui/react'; import { t, Trans } from '@lingui/macro'; +import { withI18n, withI18nProps } from '@lingui/react'; +import React, { PureComponent } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; +import { Button, Icon, LoadingPlaceholder } from '@grafana/ui'; +import { UserSession } from 'app/types'; interface Props extends withI18nProps { sessions: UserSession[]; diff --git a/public/app/features/profile/UserTeams.tsx b/public/app/features/profile/UserTeams.tsx index fc5c4bf3a94..a8af0342f8d 100644 --- a/public/app/features/profile/UserTeams.tsx +++ b/public/app/features/profile/UserTeams.tsx @@ -1,6 +1,7 @@ import React, { PureComponent } from 'react'; -import { Team } from 'app/types'; + import { LoadingPlaceholder } from '@grafana/ui'; +import { Team } from 'app/types'; export interface Props { teams: Team[]; diff --git a/public/app/features/profile/api.ts b/public/app/features/profile/api.ts index 7adc848a5ec..bb7e30d6b34 100644 --- a/public/app/features/profile/api.ts +++ b/public/app/features/profile/api.ts @@ -1,8 +1,9 @@ import { getBackendSrv } from '@grafana/runtime'; -import { ChangePasswordFields, ProfileUpdateFields } from './types'; import { Team, UserDTO, UserOrg, UserSession } from '../../types'; +import { ChangePasswordFields, ProfileUpdateFields } from './types'; + async function changePassword(payload: ChangePasswordFields): Promise { try { await getBackendSrv().put('/api/user/password', payload); diff --git a/public/app/features/profile/routes.tsx b/public/app/features/profile/routes.tsx index 48029f75dc8..cd101b79164 100644 --- a/public/app/features/profile/routes.tsx +++ b/public/app/features/profile/routes.tsx @@ -1,7 +1,8 @@ +import { uniq } from 'lodash'; + import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; import { config } from 'app/core/config'; import { RouteDescriptor } from 'app/core/navigation/types'; -import { uniq } from 'lodash'; const profileRoutes: RouteDescriptor[] = [ { diff --git a/public/app/features/profile/state/actions.ts b/public/app/features/profile/state/actions.ts index 2155b1088b2..e1e5f25600c 100644 --- a/public/app/features/profile/state/actions.ts +++ b/public/app/features/profile/state/actions.ts @@ -1,7 +1,9 @@ import { config } from '@grafana/runtime'; -import { ChangePasswordFields, ProfileUpdateFields } from '../types'; import { ThunkResult, UserOrg } from '../../../types'; +import { api } from '../api'; +import { ChangePasswordFields, ProfileUpdateFields } from '../types'; + import { initLoadOrgs, initLoadSessions, @@ -13,7 +15,6 @@ import { userLoaded, userSessionRevoked, } from './reducers'; -import { api } from '../api'; export function changePassword(payload: ChangePasswordFields): ThunkResult { return async function (dispatch) { diff --git a/public/app/features/profile/state/reducers.test.ts b/public/app/features/profile/state/reducers.test.ts index b9bcd399ab3..6993b46283d 100644 --- a/public/app/features/profile/state/reducers.test.ts +++ b/public/app/features/profile/state/reducers.test.ts @@ -1,5 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { OrgRole, TeamPermissionLevel } from '../../../types'; + import { initialUserState, orgsLoaded, diff --git a/public/app/features/profile/state/reducers.ts b/public/app/features/profile/state/reducers.ts index 90bde6f695b..a78ed3d5316 100644 --- a/public/app/features/profile/state/reducers.ts +++ b/public/app/features/profile/state/reducers.ts @@ -1,10 +1,10 @@ -import { isEmpty, isString, set } from 'lodash'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { dateTimeFormatTimeAgo, setWeekStart, TimeZone } from '@grafana/data'; +import { isEmpty, isString, set } from 'lodash'; -import { Team, ThunkResult, UserDTO, UserOrg, UserSession } from 'app/types'; +import { dateTimeFormatTimeAgo, setWeekStart, TimeZone } from '@grafana/data'; import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; +import { Team, ThunkResult, UserDTO, UserOrg, UserSession } from 'app/types'; export interface UserState { orgId: number; diff --git a/public/app/features/query/components/HelpToggle.tsx b/public/app/features/query/components/HelpToggle.tsx index 158cb5b8bd5..e06fbbb38a8 100644 --- a/public/app/features/query/components/HelpToggle.tsx +++ b/public/app/features/query/components/HelpToggle.tsx @@ -1,8 +1,9 @@ -import { GrafanaTheme } from '@grafana/data'; -import { Icon, InfoBox, stylesFactory, useTheme } from '@grafana/ui'; import { css, cx } from '@emotion/css'; import React, { useState } from 'react'; +import { GrafanaTheme } from '@grafana/data'; +import { Icon, InfoBox, stylesFactory, useTheme } from '@grafana/ui'; + const getStyles = stylesFactory((theme: GrafanaTheme) => ({ infoBox: css` margin-top: ${theme.spacing.xs}; diff --git a/public/app/features/query/components/QueryEditorRow.test.ts b/public/app/features/query/components/QueryEditorRow.test.ts index 34953929ce2..e5199482b05 100644 --- a/public/app/features/query/components/QueryEditorRow.test.ts +++ b/public/app/features/query/components/QueryEditorRow.test.ts @@ -1,4 +1,5 @@ import { DataQueryRequest, dateTime, LoadingState, PanelData, toDataFrame } from '@grafana/data'; + import { filterPanelDataToQuery } from './QueryEditorRow'; function makePretendRequest(requestId: string, subRequests?: DataQueryRequest[]): DataQueryRequest { diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index d7a6f156d2e..4ed1185e9a2 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -1,12 +1,9 @@ // Libraries -import React, { PureComponent, ReactNode } from 'react'; import classNames from 'classnames'; import { cloneDeep, has } from 'lodash'; +import React, { PureComponent, ReactNode } from 'react'; + // Utils & Services -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { AngularComponent, getAngularLoader } from '@grafana/runtime'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; import { CoreApp, DataQuery, @@ -21,17 +18,22 @@ import { TimeRange, toLegacyResponseData, } from '@grafana/data'; -import { QueryEditorRowHeader } from './QueryEditorRowHeader'; +import { selectors } from '@grafana/e2e-selectors'; +import { AngularComponent, getAngularLoader } from '@grafana/runtime'; +import { ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui'; +import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; +import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { QueryOperationRow, QueryOperationRowRenderProps, } from 'app/core/components/QueryOperationRow/QueryOperationRow'; -import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; -import { selectors } from '@grafana/e2e-selectors'; -import { PanelModel } from 'app/features/dashboard/state/PanelModel'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; -import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; +import { PanelModel } from 'app/features/dashboard/state/PanelModel'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; + import { RowActionComponents } from './QueryActionComponent'; +import { QueryEditorRowHeader } from './QueryEditorRowHeader'; import { QueryErrorAlert } from './QueryErrorAlert'; interface Props { diff --git a/public/app/features/query/components/QueryEditorRowHeader.test.tsx b/public/app/features/query/components/QueryEditorRowHeader.test.tsx index 8e6d616be84..736c8bc2a95 100644 --- a/public/app/features/query/components/QueryEditorRowHeader.test.tsx +++ b/public/app/features/query/components/QueryEditorRowHeader.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { Props, QueryEditorRowHeader } from './QueryEditorRowHeader'; +import React from 'react'; + import { DataSourceInstanceSettings } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { mockDataSource } from 'app/features/alerting/unified/mocks'; import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; +import { Props, QueryEditorRowHeader } from './QueryEditorRowHeader'; + const mockDS = mockDataSource({ name: 'CloudManager', type: DataSourceType.Alertmanager, diff --git a/public/app/features/query/components/QueryEditorRowHeader.tsx b/public/app/features/query/components/QueryEditorRowHeader.tsx index 13714dba70a..20c8d86e900 100644 --- a/public/app/features/query/components/QueryEditorRowHeader.tsx +++ b/public/app/features/query/components/QueryEditorRowHeader.tsx @@ -1,9 +1,10 @@ -import React, { ReactNode, useState } from 'react'; import { css, cx } from '@emotion/css'; +import React, { ReactNode, useState } from 'react'; + import { DataQuery, DataSourceInstanceSettings, GrafanaTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { DataSourcePicker } from '@grafana/runtime'; import { Icon, Input, FieldValidationMessage, useStyles } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; export interface Props { query: TQuery; diff --git a/public/app/features/query/components/QueryEditorRows.tsx b/public/app/features/query/components/QueryEditorRows.tsx index 575569fc59c..b81512e7eaf 100644 --- a/public/app/features/query/components/QueryEditorRows.tsx +++ b/public/app/features/query/components/QueryEditorRows.tsx @@ -1,7 +1,6 @@ -// Libraries import React, { PureComponent } from 'react'; +import { DragDropContext, DragStart, Droppable, DropResult } from 'react-beautiful-dnd'; -// Types import { CoreApp, DataQuery, @@ -10,10 +9,10 @@ import { HistoryItem, PanelData, } from '@grafana/data'; -import { QueryEditorRow } from './QueryEditorRow'; -import { DragDropContext, DragStart, Droppable, DropResult } from 'react-beautiful-dnd'; import { getDataSourceSrv, reportInteraction } from '@grafana/runtime'; +import { QueryEditorRow } from './QueryEditorRow'; + interface Props { // The query configuration queries: DataQuery[]; diff --git a/public/app/features/query/components/QueryErrorAlert.tsx b/public/app/features/query/components/QueryErrorAlert.tsx index c7cfd73909b..3a31cdadadf 100644 --- a/public/app/features/query/components/QueryErrorAlert.tsx +++ b/public/app/features/query/components/QueryErrorAlert.tsx @@ -1,7 +1,8 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { DataQueryError, GrafanaTheme2 } from '@grafana/data'; import { Icon, useStyles2 } from '@grafana/ui'; -import { css } from '@emotion/css'; export interface Props { error: DataQueryError; diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index a28f71cc65d..9d784f45682 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -1,6 +1,17 @@ -// Libraries +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; -// Components +import { Unsubscribable } from 'rxjs'; + +import { + DataQuery, + DataSourceApi, + DataSourceInstanceSettings, + getDefaultTimeRange, + LoadingState, + PanelData, +} from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { DataSourcePicker, getDataSourceSrv } from '@grafana/runtime'; import { Button, CustomScrollbar, @@ -10,33 +21,21 @@ import { ScrollbarPosition, stylesFactory, } from '@grafana/ui'; -import { DataSourcePicker, getDataSourceSrv } from '@grafana/runtime'; -import { QueryEditorRows } from './QueryEditorRows'; -// Services -import { backendSrv } from 'app/core/services/backend_srv'; -import config from 'app/core/config'; -// Types -import { - DataQuery, - DataSourceApi, - DataSourceInstanceSettings, - getDefaultTimeRange, - LoadingState, - PanelData, -} from '@grafana/data'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; +import config from 'app/core/config'; +import { backendSrv } from 'app/core/services/backend_srv'; import { addQuery } from 'app/core/utils/query'; -import { Unsubscribable } from 'rxjs'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; -import { selectors } from '@grafana/e2e-selectors'; -import { PanelQueryRunner } from '../state/PanelQueryRunner'; -import { QueryGroupOptionsEditor } from './QueryGroupOptions'; import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; -import { css } from '@emotion/css'; import { QueryGroupOptions } from 'app/types'; -import { GroupActionComponents } from './QueryActionComponent'; + +import { PanelQueryRunner } from '../state/PanelQueryRunner'; import { updateQueries } from '../state/updateQueries'; +import { GroupActionComponents } from './QueryActionComponent'; +import { QueryEditorRows } from './QueryEditorRows'; +import { QueryGroupOptionsEditor } from './QueryGroupOptions'; + interface Props { queryRunner: PanelQueryRunner; options: QueryGroupOptions; diff --git a/public/app/features/query/components/QueryGroupOptions.tsx b/public/app/features/query/components/QueryGroupOptions.tsx index 202bcf29bbd..371851a7011 100644 --- a/public/app/features/query/components/QueryGroupOptions.tsx +++ b/public/app/features/query/components/QueryGroupOptions.tsx @@ -1,16 +1,10 @@ -// Libraries +import { css } from '@emotion/css'; import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; -// Utils import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; - -// Components import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui'; - -// Types import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { config } from 'app/core/config'; -import { css } from '@emotion/css'; import { QueryGroupOptions } from 'app/types'; interface Props { diff --git a/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.test.ts b/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.test.ts index 93426591994..6c75831bb31 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.test.ts @@ -1,11 +1,12 @@ import { AlertState, AlertStateInfo, getDefaultTimeRange, TimeRange } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; -import { DashboardQueryRunnerOptions } from './types'; -import { AlertStatesWorker } from './AlertStatesWorker'; import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; import * as store from '../../../../store/store'; +import { AlertStatesWorker } from './AlertStatesWorker'; +import { DashboardQueryRunnerOptions } from './types'; + jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), getBackendSrv: () => backendSrv, diff --git a/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.ts b/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.ts index 698f05d7165..edefd0710e0 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AlertStatesWorker.ts @@ -1,7 +1,9 @@ -import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult } from './types'; import { from, Observable } from 'rxjs'; -import { getBackendSrv } from '@grafana/runtime'; import { catchError, map } from 'rxjs/operators'; + +import { getBackendSrv } from '@grafana/runtime'; + +import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult } from './types'; import { emptyResult, handleDashboardQueryRunnerWorkerError } from './utils'; export class AlertStatesWorker implements DashboardQueryRunnerWorker { diff --git a/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.test.ts b/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.test.ts index 220363dde2f..9e04b783c8d 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.test.ts @@ -1,12 +1,14 @@ +import { Observable, of, throwError } from 'rxjs'; + import { getDefaultTimeRange } from '@grafana/data'; -import { AnnotationsQueryRunner } from './AnnotationsQueryRunner'; -import { AnnotationQueryRunnerOptions } from './types'; import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; import * as store from '../../../../store/store'; import * as annotationsSrv from '../../../annotations/executeAnnotationQuery'; -import { Observable, of, throwError } from 'rxjs'; + +import { AnnotationsQueryRunner } from './AnnotationsQueryRunner'; import { toAsyncOfResult } from './testHelpers'; +import { AnnotationQueryRunnerOptions } from './types'; function getDefaultOptions(): AnnotationQueryRunnerOptions { const annotation: any = {}; diff --git a/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts b/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts index 458656615b2..4eadff44e59 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts @@ -1,10 +1,12 @@ import { Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; + import { AnnotationEvent, DataSourceApi } from '@grafana/data'; -import { AnnotationQueryRunner, AnnotationQueryRunnerOptions } from './types'; -import { PanelModel } from '../../../dashboard/state'; import { executeAnnotationQuery } from '../../../annotations/executeAnnotationQuery'; +import { PanelModel } from '../../../dashboard/state'; + +import { AnnotationQueryRunner, AnnotationQueryRunnerOptions } from './types'; import { handleAnnotationQueryRunnerError } from './utils'; export class AnnotationsQueryRunner implements AnnotationQueryRunner { diff --git a/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.test.ts b/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.test.ts index 8f6917004c9..c82ff012f6e 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.test.ts @@ -1,15 +1,17 @@ import { Subject, throwError } from 'rxjs'; +import { delay } from 'rxjs/operators'; + +import { AnnotationQuery } from '@grafana/data'; import { setDataSourceSrv } from '@grafana/runtime'; -import { AnnotationsWorker } from './AnnotationsWorker'; -import * as annotationsSrv from '../../../annotations/executeAnnotationQuery'; -import { getDefaultOptions, LEGACY_DS_NAME, NEXT_GEN_DS_NAME, toAsyncOfResult } from './testHelpers'; import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; +import * as annotationsSrv from '../../../annotations/executeAnnotationQuery'; + +import { AnnotationsWorker } from './AnnotationsWorker'; import { createDashboardQueryRunner, setDashboardQueryRunnerFactory } from './DashboardQueryRunner'; -import { emptyResult } from './utils'; +import { getDefaultOptions, LEGACY_DS_NAME, NEXT_GEN_DS_NAME, toAsyncOfResult } from './testHelpers'; import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorkerResult } from './types'; -import { AnnotationQuery } from '@grafana/data'; -import { delay } from 'rxjs/operators'; +import { emptyResult } from './utils'; function getTestContext(dataSourceSrvRejects = false) { jest.clearAllMocks(); diff --git a/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.ts b/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.ts index 7233303b84c..4744483d2a1 100644 --- a/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.ts +++ b/public/app/features/query/state/DashboardQueryRunner/AnnotationsWorker.ts @@ -1,9 +1,15 @@ import { cloneDeep } from 'lodash'; import { from, merge, Observable, of } from 'rxjs'; import { catchError, filter, finalize, map, mergeAll, mergeMap, reduce, takeUntil } from 'rxjs/operators'; -import { getDataSourceSrv } from '@grafana/runtime'; -import { AnnotationQuery, DataSourceApi } from '@grafana/data'; +import { AnnotationQuery, DataSourceApi } from '@grafana/data'; +import { getDataSourceSrv } from '@grafana/runtime'; + +import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events'; + +import { AnnotationsQueryRunner } from './AnnotationsQueryRunner'; +import { getDashboardQueryRunner } from './DashboardQueryRunner'; +import { LegacyAnnotationQueryRunner } from './LegacyAnnotationQueryRunner'; import { AnnotationQueryRunner, DashboardQueryRunnerOptions, @@ -11,10 +17,6 @@ import { DashboardQueryRunnerWorkerResult, } from './types'; import { emptyResult, handleDatasourceSrvError, translateQueryResult } from './utils'; -import { LegacyAnnotationQueryRunner } from './LegacyAnnotationQueryRunner'; -import { AnnotationsQueryRunner } from './AnnotationsQueryRunner'; -import { AnnotationQueryFinished, AnnotationQueryStarted } from '../../../../types/events'; -import { getDashboardQueryRunner } from './DashboardQueryRunner'; export class AnnotationsWorker implements DashboardQueryRunnerWorker { constructor( diff --git a/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.test.ts b/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.test.ts index ba65d463d4f..43c0509e6eb 100644 --- a/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.test.ts @@ -1,14 +1,16 @@ import { throwError } from 'rxjs'; import { delay, first } from 'rxjs/operators'; -import { setDataSourceSrv } from '@grafana/runtime'; -import { AlertState, AlertStateInfo } from '@grafana/data'; -import * as annotationsSrv from '../../../annotations/executeAnnotationQuery'; -import { getDefaultOptions, LEGACY_DS_NAME, NEXT_GEN_DS_NAME, toAsyncOfResult } from './testHelpers'; -import { backendSrv } from '../../../../core/services/backend_srv'; -import { DashboardQueryRunner, DashboardQueryRunnerResult } from './types'; +import { AlertState, AlertStateInfo } from '@grafana/data'; +import { setDataSourceSrv } from '@grafana/runtime'; + import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; +import { backendSrv } from '../../../../core/services/backend_srv'; +import * as annotationsSrv from '../../../annotations/executeAnnotationQuery'; + import { createDashboardQueryRunner } from './DashboardQueryRunner'; +import { getDefaultOptions, LEGACY_DS_NAME, NEXT_GEN_DS_NAME, toAsyncOfResult } from './testHelpers'; +import { DashboardQueryRunner, DashboardQueryRunnerResult } from './types'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts b/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts index b7756e6295c..42538db0392 100644 --- a/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts +++ b/public/app/features/query/state/DashboardQueryRunner/DashboardQueryRunner.ts @@ -1,8 +1,18 @@ import { merge, Observable, ReplaySubject, Subject, Subscription, timer, Unsubscribable } from 'rxjs'; import { finalize, map, mapTo, mergeAll, reduce, share, takeUntil } from 'rxjs/operators'; -import { AnnotationQuery } from '@grafana/data'; +import { AnnotationQuery } from '@grafana/data'; +import { RefreshEvent } from '@grafana/runtime'; +import { config } from 'app/core/config'; import { dedupAnnotations } from 'app/features/annotations/events_processing'; + +import { getTimeSrv, TimeSrv } from '../../../dashboard/services/TimeSrv'; +import { DashboardModel } from '../../../dashboard/state'; + +import { AlertStatesWorker } from './AlertStatesWorker'; +import { AnnotationsWorker } from './AnnotationsWorker'; +import { SnapshotWorker } from './SnapshotWorker'; +import { UnifiedAlertStatesWorker } from './UnifiedAlertStatesWorker'; import { DashboardQueryRunner, DashboardQueryRunnerOptions, @@ -10,15 +20,7 @@ import { DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult, } from './types'; -import { AlertStatesWorker } from './AlertStatesWorker'; -import { SnapshotWorker } from './SnapshotWorker'; -import { AnnotationsWorker } from './AnnotationsWorker'; import { getAnnotationsByPanelId } from './utils'; -import { DashboardModel } from '../../../dashboard/state'; -import { getTimeSrv, TimeSrv } from '../../../dashboard/services/TimeSrv'; -import { RefreshEvent } from '@grafana/runtime'; -import { config } from 'app/core/config'; -import { UnifiedAlertStatesWorker } from './UnifiedAlertStatesWorker'; class DashboardQueryRunnerImpl implements DashboardQueryRunner { private readonly results: ReplaySubject; diff --git a/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.test.ts b/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.test.ts index 082bf648edd..9111583e8df 100644 --- a/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.test.ts @@ -1,9 +1,10 @@ import { getDefaultTimeRange } from '@grafana/data'; +import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; +import * as store from '../../../../store/store'; + import { LegacyAnnotationQueryRunner } from './LegacyAnnotationQueryRunner'; import { AnnotationQueryRunnerOptions } from './types'; -import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; -import * as store from '../../../../store/store'; function getDefaultOptions(annotationQuery?: jest.Mock): AnnotationQueryRunnerOptions { const annotation: any = {}; diff --git a/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts b/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts index 47d5ee5a533..5d94911b9ab 100644 --- a/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts +++ b/public/app/features/query/state/DashboardQueryRunner/LegacyAnnotationQueryRunner.ts @@ -1,5 +1,6 @@ import { from, Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; + import { AnnotationEvent, DataSourceApi } from '@grafana/data'; import { AnnotationQueryRunner, AnnotationQueryRunnerOptions } from './types'; diff --git a/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.test.ts b/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.test.ts index e9f669a5ef1..f8f1f499869 100644 --- a/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.test.ts @@ -1,7 +1,7 @@ import { AnnotationEvent, getDefaultTimeRange } from '@grafana/data'; -import { DashboardQueryRunnerOptions } from './types'; import { SnapshotWorker } from './SnapshotWorker'; +import { DashboardQueryRunnerOptions } from './types'; function getDefaultOptions(): DashboardQueryRunnerOptions { const dashboard: any = {}; diff --git a/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.ts b/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.ts index 2ed6b42cf33..861b1cbd819 100644 --- a/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.ts +++ b/public/app/features/query/state/DashboardQueryRunner/SnapshotWorker.ts @@ -1,9 +1,11 @@ import { Observable, of } from 'rxjs'; + import { AnnotationEvent } from '@grafana/data'; +import { DashboardModel } from '../../../dashboard/state'; + import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult } from './types'; import { emptyResult, getAnnotationsByPanelId, translateQueryResult } from './utils'; -import { DashboardModel } from '../../../dashboard/state'; export class SnapshotWorker implements DashboardQueryRunnerWorker { canWork({ dashboard }: DashboardQueryRunnerOptions): boolean { diff --git a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts index ebb2ee19c7c..f2d1c4eaf8c 100644 --- a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts @@ -1,13 +1,15 @@ +import { lastValueFrom } from 'rxjs'; + import { AlertState, getDefaultTimeRange, TimeRange } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; +import { Annotation } from 'app/features/alerting/unified/utils/constants'; +import { PromAlertingRuleState, PromRuleDTO, PromRulesResponse, PromRuleType } from 'app/types/unified-alerting-dto'; -import { DashboardQueryRunnerOptions } from './types'; -import { UnifiedAlertStatesWorker } from './UnifiedAlertStatesWorker'; import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; import * as store from '../../../../store/store'; -import { PromAlertingRuleState, PromRuleDTO, PromRulesResponse, PromRuleType } from 'app/types/unified-alerting-dto'; -import { Annotation } from 'app/features/alerting/unified/utils/constants'; -import { lastValueFrom } from 'rxjs'; + +import { UnifiedAlertStatesWorker } from './UnifiedAlertStatesWorker'; +import { DashboardQueryRunnerOptions } from './types'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts index fc43b6b93a7..a9100ce3e60 100644 --- a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts +++ b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts @@ -1,12 +1,14 @@ -import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult } from './types'; import { from, Observable } from 'rxjs'; -import { getBackendSrv } from '@grafana/runtime'; import { catchError, map } from 'rxjs/operators'; -import { emptyResult, handleDashboardQueryRunnerWorkerError } from './utils'; -import { PromAlertingRuleState, PromRulesResponse } from 'app/types/unified-alerting-dto'; + import { AlertState, AlertStateInfo } from '@grafana/data'; -import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; +import { getBackendSrv } from '@grafana/runtime'; import { Annotation } from 'app/features/alerting/unified/utils/constants'; +import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; +import { PromAlertingRuleState, PromRulesResponse } from 'app/types/unified-alerting-dto'; + +import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult } from './types'; +import { emptyResult, handleDashboardQueryRunnerWorkerError } from './utils'; export class UnifiedAlertStatesWorker implements DashboardQueryRunnerWorker { // maps dashboard uid to wether it has alert rules. diff --git a/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts b/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts index 7891c23e604..0015adfc081 100644 --- a/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts +++ b/public/app/features/query/state/DashboardQueryRunner/testHelpers.ts @@ -1,7 +1,9 @@ import { asyncScheduler, Observable, of, scheduled } from 'rxjs'; -import { DashboardQueryRunnerOptions } from './types'; + import { AnnotationEvent, getDefaultTimeRange } from '@grafana/data'; +import { DashboardQueryRunnerOptions } from './types'; + // function that creates an async of result Observable export function toAsyncOfResult(result: any): Observable { return scheduled(of(result), asyncScheduler); diff --git a/public/app/features/query/state/DashboardQueryRunner/types.ts b/public/app/features/query/state/DashboardQueryRunner/types.ts index 12f78b23077..ff19d443506 100644 --- a/public/app/features/query/state/DashboardQueryRunner/types.ts +++ b/public/app/features/query/state/DashboardQueryRunner/types.ts @@ -1,4 +1,5 @@ import { Observable } from 'rxjs'; + import { AlertStateInfo, AnnotationEvent, AnnotationQuery, DataSourceApi, TimeRange } from '@grafana/data'; import { DashboardModel } from '../../../dashboard/state'; diff --git a/public/app/features/query/state/DashboardQueryRunner/utils.ts b/public/app/features/query/state/DashboardQueryRunner/utils.ts index f3f88682059..2e17bf360b2 100644 --- a/public/app/features/query/state/DashboardQueryRunner/utils.ts +++ b/public/app/features/query/state/DashboardQueryRunner/utils.ts @@ -1,11 +1,13 @@ import { cloneDeep } from 'lodash'; import { Observable, of } from 'rxjs'; + import { AnnotationEvent, AnnotationQuery, DataFrame, DataFrameView, DataSourceApi } from '@grafana/data'; import { config, toDataQueryError } from '@grafana/runtime'; - import { dispatch } from 'app/store/store'; + import { createErrorNotification } from '../../../../core/copy/appNotification'; import { notifyApp } from '../../../../core/reducers/appNotification'; + import { DashboardQueryRunnerWorkerResult } from './types'; export function handleAnnotationQueryRunnerError(err: any): Observable { diff --git a/public/app/features/query/state/PanelQueryRunner.test.ts b/public/app/features/query/state/PanelQueryRunner.test.ts index 40ad3b52c19..d86db370a6d 100644 --- a/public/app/features/query/state/PanelQueryRunner.test.ts +++ b/public/app/features/query/state/PanelQueryRunner.test.ts @@ -1,16 +1,19 @@ const applyFieldOverridesMock = jest.fn(); // needs to be first in this file import { Subject } from 'rxjs'; + // Importing this way to be able to spy on grafana/data import * as grafanaData from '@grafana/data'; -import { DashboardModel } from '../../dashboard/state/index'; import { setDataSourceSrv, setEchoSrv } from '@grafana/runtime'; + import { Echo } from '../../../core/services/echo/Echo'; -import { emptyResult } from './DashboardQueryRunner/utils'; +import { DashboardModel } from '../../dashboard/state/index'; + import { createDashboardQueryRunner, setDashboardQueryRunnerFactory, } from './DashboardQueryRunner/DashboardQueryRunner'; +import { emptyResult } from './DashboardQueryRunner/utils'; import { PanelQueryRunner } from './PanelQueryRunner'; jest.mock('@grafana/data', () => ({ diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index 90336b3f23b..f7ceea18895 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -1,15 +1,7 @@ -// Libraries import { cloneDeep } from 'lodash'; import { MonoTypeOperatorFunction, Observable, of, ReplaySubject, Unsubscribable } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; -// Services & Utils -import { getTemplateSrv } from '@grafana/runtime'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { preProcessPanelData, runRequest } from './runRequest'; -import { isSharedDashboardQuery, runSharedRequest } from '../../../plugins/datasource/dashboard'; - -// Types import { applyFieldOverrides, compareArrayValues, @@ -33,11 +25,17 @@ import { toDataFrame, transformDataFrame, } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; +import { StreamingDataFrame } from 'app/features/live/data/StreamingDataFrame'; +import { isStreamingDataFrame } from 'app/features/live/data/utils'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; + +import { isSharedDashboardQuery, runSharedRequest } from '../../../plugins/datasource/dashboard'; +import { PanelModel } from '../../dashboard/state'; + import { getDashboardQueryRunner } from './DashboardQueryRunner/DashboardQueryRunner'; import { mergePanelAndDashData } from './mergePanelAndDashData'; -import { PanelModel } from '../../dashboard/state'; -import { isStreamingDataFrame } from 'app/features/live/data/utils'; -import { StreamingDataFrame } from 'app/features/live/data/StreamingDataFrame'; +import { preProcessPanelData, runRequest } from './runRequest'; export interface QueryRunnerOptions< TQuery extends DataQuery = DataQuery, diff --git a/public/app/features/query/state/QueryRunner.ts b/public/app/features/query/state/QueryRunner.ts index a437e7ec554..8002bd7b860 100644 --- a/public/app/features/query/state/QueryRunner.ts +++ b/public/app/features/query/state/QueryRunner.ts @@ -1,3 +1,7 @@ +import { cloneDeep } from 'lodash'; +import { from, Observable, ReplaySubject, Unsubscribable } from 'rxjs'; +import { first } from 'rxjs/operators'; + import { CoreApp, DataQueryRequest, @@ -12,9 +16,7 @@ import { } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { cloneDeep } from 'lodash'; -import { from, Observable, ReplaySubject, Unsubscribable } from 'rxjs'; -import { first } from 'rxjs/operators'; + import { getNextRequestId } from './PanelQueryRunner'; import { setStructureRevision } from './processing/revision'; import { preProcessPanelData, runRequest } from './runRequest'; diff --git a/public/app/features/query/state/mergePanelAndDashData.test.ts b/public/app/features/query/state/mergePanelAndDashData.test.ts index b559ba47878..06c3fff4ea1 100644 --- a/public/app/features/query/state/mergePanelAndDashData.test.ts +++ b/public/app/features/query/state/mergePanelAndDashData.test.ts @@ -1,7 +1,9 @@ -import { AlertState, getDefaultTimeRange, LoadingState, PanelData, toDataFrame } from '@grafana/data'; -import { mergePanelAndDashData } from './mergePanelAndDashData'; import { TestScheduler } from 'rxjs/testing'; +import { AlertState, getDefaultTimeRange, LoadingState, PanelData, toDataFrame } from '@grafana/data'; + +import { mergePanelAndDashData } from './mergePanelAndDashData'; + function getTestContext() { const timeRange = getDefaultTimeRange(); const panelData: PanelData = { diff --git a/public/app/features/query/state/mergePanelAndDashData.ts b/public/app/features/query/state/mergePanelAndDashData.ts index 1ee11a541b4..f661b7dd649 100644 --- a/public/app/features/query/state/mergePanelAndDashData.ts +++ b/public/app/features/query/state/mergePanelAndDashData.ts @@ -1,8 +1,10 @@ import { combineLatest, Observable, of } from 'rxjs'; -import { ArrayDataFrame, PanelData } from '@grafana/data'; -import { DashboardQueryRunnerResult } from './DashboardQueryRunner/types'; import { mergeMap } from 'rxjs/operators'; +import { ArrayDataFrame, PanelData } from '@grafana/data'; + +import { DashboardQueryRunnerResult } from './DashboardQueryRunner/types'; + export function mergePanelAndDashData( panelObservable: Observable, dashObservable: Observable diff --git a/public/app/features/query/state/processing/canceler.ts b/public/app/features/query/state/processing/canceler.ts index 3e0498b108e..254395adabc 100644 --- a/public/app/features/query/state/processing/canceler.ts +++ b/public/app/features/query/state/processing/canceler.ts @@ -1,7 +1,8 @@ -import { BackendSrv } from 'app/core/services/backend_srv'; import { MonoTypeOperatorFunction } from 'rxjs'; import { finalize } from 'rxjs/operators'; +import { BackendSrv } from 'app/core/services/backend_srv'; + export function cancelNetworkRequestsOnUnsubscribe( backendSrv: BackendSrv, requestId: string | undefined diff --git a/public/app/features/query/state/queryAnalytics.test.ts b/public/app/features/query/state/queryAnalytics.test.ts index 7af84e4fcb8..e9fa2e159bd 100644 --- a/public/app/features/query/state/queryAnalytics.test.ts +++ b/public/app/features/query/state/queryAnalytics.test.ts @@ -1,7 +1,9 @@ +import { CoreApp, DataFrame, DataQueryRequest, DataSourceApi, dateTime, LoadingState, PanelData } from '@grafana/data'; import { MetaAnalyticsEventName, reportMetaAnalytics } from '@grafana/runtime'; -import { CoreApp, DataQueryRequest, DataSourceApi, dateTime, LoadingState, PanelData } from '@grafana/data'; + +import { DashboardModel } from '../../dashboard/state'; + import { emitDataRequestEvent } from './queryAnalytics'; -import { DashboardModel } from '../../dashboard/state/DashboardModel'; beforeEach(() => { jest.clearAllMocks(); @@ -40,7 +42,39 @@ jest.mock('@grafana/data', () => ({ }, })); -function getTestData(requestApp: string): PanelData { +const partiallyCachedSeries = [ + { + refId: 'A', + meta: { + isCachedResponse: true, + }, + fields: [], + length: 0, + }, + { + refId: 'B', + fields: [], + length: 0, + }, +]; + +const multipleDataframesWithSameRefId = [ + { + refId: 'A', + meta: { + isCachedResponse: true, + }, + fields: [], + length: 0, + }, + { + refId: 'A', + fields: [], + length: 0, + }, +]; + +function getTestData(requestApp: string, series: DataFrame[] = []): PanelData { const now = dateTime(); return { request: { @@ -50,7 +84,7 @@ function getTestData(requestApp: string): PanelData { startTime: now.unix(), endTime: now.add(1, 's').unix(), } as DataQueryRequest, - series: [], + series, state: LoadingState.Done, timeRange: { from: dateTime(), @@ -61,10 +95,9 @@ function getTestData(requestApp: string): PanelData { } describe('emitDataRequestEvent - from a dashboard panel', () => { - const data = getTestData(CoreApp.Dashboard); - const fn = emitDataRequestEvent(datasource); it('Should report meta analytics', () => { - fn(data); + const data = getTestData(CoreApp.Dashboard); + emitDataRequestEvent(datasource)(data); expect(reportMetaAnalytics).toBeCalledTimes(1); expect(reportMetaAnalytics).toBeCalledWith( @@ -79,19 +112,71 @@ describe('emitDataRequestEvent - from a dashboard panel', () => { folderName: 'Test Folder', dataSize: 0, duration: 1, + totalQueries: 0, + cachedQueries: 0, + }) + ); + }); + + it('Should report meta analytics with counts for cached and total queries', () => { + const data = getTestData(CoreApp.Dashboard, partiallyCachedSeries); + emitDataRequestEvent(datasource)(data); + + expect(reportMetaAnalytics).toBeCalledTimes(1); + expect(reportMetaAnalytics).toBeCalledWith( + expect.objectContaining({ + eventName: MetaAnalyticsEventName.DataRequest, + datasourceName: datasource.name, + datasourceId: datasource.id, + panelId: 2, + dashboardId: 1, + dashboardName: 'Test Dashboard', + dashboardUid: 'test', + folderName: 'Test Folder', + dataSize: 2, + duration: 1, + totalQueries: 2, + cachedQueries: 1, + }) + ); + }); + + it('Should report meta analytics with counts for cached and total queries when same refId spread across multiple DataFrames', () => { + const data = getTestData(CoreApp.Dashboard, multipleDataframesWithSameRefId); + emitDataRequestEvent(datasource)(data); + + expect(reportMetaAnalytics).toBeCalledTimes(1); + expect(reportMetaAnalytics).toBeCalledWith( + expect.objectContaining({ + eventName: MetaAnalyticsEventName.DataRequest, + datasourceName: datasource.name, + datasourceId: datasource.id, + panelId: 2, + dashboardId: 1, + dashboardName: 'Test Dashboard', + dashboardUid: 'test', + folderName: 'Test Folder', + dataSize: 2, + duration: 1, + totalQueries: 1, + cachedQueries: 1, }) ); }); it('Should not report meta analytics twice if the request receives multiple responses', () => { + const data = getTestData(CoreApp.Dashboard); + const fn = emitDataRequestEvent(datasource); fn(data); - expect(reportMetaAnalytics).not.toBeCalled(); + fn(data); + expect(reportMetaAnalytics).toBeCalledTimes(1); }); it('Should not report meta analytics in edit mode', () => { mockGetUrlSearchParams.mockImplementationOnce(() => { return { editPanel: 2 }; }); + const data = getTestData(CoreApp.Dashboard); emitDataRequestEvent(datasource)(data); expect(reportMetaAnalytics).not.toBeCalled(); }); diff --git a/public/app/features/query/state/queryAnalytics.ts b/public/app/features/query/state/queryAnalytics.ts index 4ec30cadfff..b3e3dccd160 100644 --- a/public/app/features/query/state/queryAnalytics.ts +++ b/public/app/features/query/state/queryAnalytics.ts @@ -1,7 +1,8 @@ -import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; import { PanelData, LoadingState, DataSourceApi, CoreApp, urlUtil } from '@grafana/data'; import { reportMetaAnalytics, MetaAnalyticsEventName, DataRequestEventPayload } from '@grafana/runtime'; +import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; + export function emitDataRequestEvent(datasource: DataSourceApi) { let done = false; @@ -19,6 +20,16 @@ export function emitDataRequestEvent(datasource: DataSourceApi) { return; } + const queryCacheStatus: { [key: string]: boolean } = {}; + for (let i = 0; i < data.series.length; i++) { + const refId = data.series[i].refId; + if (refId && !queryCacheStatus[refId]) { + queryCacheStatus[refId] = data.series[i].meta?.isCachedResponse ?? false; + } + } + const totalQueries = Object.keys(queryCacheStatus).length; + const cachedQueries = Object.values(queryCacheStatus).filter((val) => val === true).length; + const eventData: DataRequestEventPayload = { eventName: MetaAnalyticsEventName.DataRequest, datasourceName: datasource.name, @@ -28,6 +39,8 @@ export function emitDataRequestEvent(datasource: DataSourceApi) { dashboardId: data.request.dashboardId, dataSize: 0, duration: data.request.endTime! - data.request.startTime, + totalQueries, + cachedQueries, }; // enrich with dashboard info diff --git a/public/app/features/query/state/runRequest.test.ts b/public/app/features/query/state/runRequest.test.ts index 8a40ef7fd08..366e0282be9 100644 --- a/public/app/features/query/state/runRequest.test.ts +++ b/public/app/features/query/state/runRequest.test.ts @@ -1,3 +1,5 @@ +import { Observable, Subscriber, Subscription } from 'rxjs'; + import { DataFrame, DataQueryRequest, @@ -8,12 +10,13 @@ import { LoadingState, PanelData, } from '@grafana/data'; -import { Observable, Subscriber, Subscription } from 'rxjs'; -import { runRequest } from './runRequest'; -import { deepFreeze } from '../../../../test/core/redux/reducerTester'; -import { DashboardModel } from '../../dashboard/state/DashboardModel'; import { setEchoSrv } from '@grafana/runtime'; + +import { deepFreeze } from '../../../../test/core/redux/reducerTester'; import { Echo } from '../../../core/services/echo/Echo'; +import { DashboardModel } from '../../dashboard/state/DashboardModel'; + +import { runRequest } from './runRequest'; jest.mock('app/core/services/backend_srv'); diff --git a/public/app/features/query/state/runRequest.ts b/public/app/features/query/state/runRequest.ts index 76a7442ab3f..768262916f7 100644 --- a/public/app/features/query/state/runRequest.ts +++ b/public/app/features/query/state/runRequest.ts @@ -1,9 +1,9 @@ // Libraries -import { from, merge, Observable, of, timer } from 'rxjs'; import { isString, map as isArray } from 'lodash'; +import { from, merge, Observable, of, timer } from 'rxjs'; import { catchError, map, mapTo, share, takeUntil, tap } from 'rxjs/operators'; + // Utils & Services -import { backendSrv } from 'app/core/services/backend_srv'; // Types import { DataFrame, @@ -21,11 +21,13 @@ import { toDataFrame, } from '@grafana/data'; import { toDataQueryError } from '@grafana/runtime'; -import { emitDataRequestEvent } from './queryAnalytics'; +import { isExpressionReference } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import { backendSrv } from 'app/core/services/backend_srv'; import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { ExpressionQuery } from 'app/features/expressions/types'; + import { cancelNetworkRequestsOnUnsubscribe } from './processing/canceler'; -import { isExpressionReference } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import { emitDataRequestEvent } from './queryAnalytics'; type MapOfResponsePackets = { [str: string]: DataQueryResponse }; diff --git a/public/app/features/query/state/updateQueries.test.ts b/public/app/features/query/state/updateQueries.test.ts index f4f8a2de817..ad87cb3be9d 100644 --- a/public/app/features/query/state/updateQueries.test.ts +++ b/public/app/features/query/state/updateQueries.test.ts @@ -5,6 +5,7 @@ import { DataSourceWithQueryImportSupport, } from '@grafana/data'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; + import { updateQueries } from './updateQueries'; const oldUidDS = { diff --git a/public/app/features/sandbox/BenchmarksPage.tsx b/public/app/features/sandbox/BenchmarksPage.tsx index ace7740745d..d8813fdbf78 100644 --- a/public/app/features/sandbox/BenchmarksPage.tsx +++ b/public/app/features/sandbox/BenchmarksPage.tsx @@ -1,6 +1,7 @@ -import { EmotionPerfTest, VerticalGroup } from '@grafana/ui'; import React, { FC } from 'react'; +import { EmotionPerfTest, VerticalGroup } from '@grafana/ui'; + export const BenchmarksPage: FC = () => { return ( diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx index 360402b78dd..7d34b5a9e4e 100644 --- a/public/app/features/sandbox/TestStuffPage.tsx +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -1,3 +1,7 @@ +import React, { FC, useMemo, useState } from 'react'; +import { useObservable } from 'react-use'; +import AutoSizer from 'react-virtualized-auto-sizer'; + import { ApplyFieldOverrideOptions, DataTransformerConfig, @@ -6,16 +10,15 @@ import { NavModelItem, PanelData, } from '@grafana/data'; -import { Table } from '@grafana/ui'; +import { Button, Table } from '@grafana/ui'; import { config } from 'app/core/config'; -import React, { FC, useMemo, useState } from 'react'; -import { useObservable } from 'react-use'; +import { useAppNotification } from 'app/core/copy/appNotification'; +import { QueryGroupOptions } from 'app/types'; + +import Page from '../../core/components/Page/Page'; +import { PanelRenderer } from '../panel/components/PanelRenderer'; import { QueryGroup } from '../query/components/QueryGroup'; import { PanelQueryRunner } from '../query/state/PanelQueryRunner'; -import { QueryGroupOptions } from 'app/types'; -import Page from '../../core/components/Page/Page'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { PanelRenderer } from '../panel/components/PanelRenderer'; interface State { queryRunner: PanelQueryRunner; @@ -58,6 +61,8 @@ export const TestStuffPage: FC = () => { url: 'sandbox/test', }; + const notifyApp = useAppNotification(); + return ( @@ -90,6 +95,23 @@ export const TestStuffPage: FC = () => { onOptionsChange={onOptionsChange} />
+
+ + + +
); diff --git a/public/app/features/search/components/ActionRow.tsx b/public/app/features/search/components/ActionRow.tsx index dc6d0af3294..ef9c5d13c65 100644 --- a/public/app/features/search/components/ActionRow.tsx +++ b/public/app/features/search/components/ActionRow.tsx @@ -1,12 +1,14 @@ -import React, { FC, ChangeEvent, FormEvent } from 'react'; import { css } from '@emotion/css'; -import { HorizontalGroup, RadioButtonGroup, stylesFactory, useTheme, Checkbox, InlineSwitch } from '@grafana/ui'; +import React, { FC, ChangeEvent, FormEvent } from 'react'; + import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { HorizontalGroup, RadioButtonGroup, stylesFactory, useTheme, Checkbox, InlineSwitch } from '@grafana/ui'; import { SortPicker } from 'app/core/components/Select/SortPicker'; import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; import { SearchSrv } from 'app/core/services/search_srv'; + import { DashboardQuery, SearchLayout } from '../types'; -import { config } from '@grafana/runtime'; export const layoutOptions = [ { value: SearchLayout.Folders, icon: 'folder', ariaLabel: 'View by folders' }, diff --git a/public/app/features/search/components/ConfirmDeleteModal.tsx b/public/app/features/search/components/ConfirmDeleteModal.tsx index d4882e1a2eb..2a5e7fcc61a 100644 --- a/public/app/features/search/components/ConfirmDeleteModal.tsx +++ b/public/app/features/search/components/ConfirmDeleteModal.tsx @@ -1,10 +1,12 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { ConfirmModal, stylesFactory, useTheme } from '@grafana/ui'; +import { deleteFoldersAndDashboards } from 'app/features/manage-dashboards/state/actions'; + import { DashboardSection, OnDeleteItems } from '../types'; import { getCheckedUids } from '../utils'; -import { deleteFoldersAndDashboards } from 'app/features/manage-dashboards/state/actions'; interface Props { onDeleteItems: OnDeleteItems; diff --git a/public/app/features/search/components/DashboardActions.tsx b/public/app/features/search/components/DashboardActions.tsx index f7b2acc2c25..95b8f6e04b9 100644 --- a/public/app/features/search/components/DashboardActions.tsx +++ b/public/app/features/search/components/DashboardActions.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { HorizontalGroup, LinkButton } from '@grafana/ui'; export interface Props { diff --git a/public/app/features/search/components/DashboardListPage.tsx b/public/app/features/search/components/DashboardListPage.tsx index 3bf90987135..5e28073914d 100644 --- a/public/app/features/search/components/DashboardListPage.tsx +++ b/public/app/features/search/components/DashboardListPage.tsx @@ -1,14 +1,17 @@ import React, { FC, memo } from 'react'; -import { useAsync } from 'react-use'; import { connect, MapStateToProps } from 'react-redux'; +import { useAsync } from 'react-use'; + import { NavModel, locationUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { FolderDTO, StoreState } from 'app/types'; -import { getNavModel } from 'app/core/selectors/navModel'; import Page from 'app/core/components/Page/Page'; -import { loadFolderPage } from '../loaders'; -import ManageDashboards from './ManageDashboards'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { FolderDTO, StoreState } from 'app/types'; + import { GrafanaRouteComponentProps } from '../../../core/navigation/types'; +import { loadFolderPage } from '../loaders'; + +import ManageDashboards from './ManageDashboards'; export interface DashboardListPageRouteParams { uid?: string; diff --git a/public/app/features/search/components/DashboardSearch.test.tsx b/public/app/features/search/components/DashboardSearch.test.tsx index 8004154fc21..0ead5d5cc4c 100644 --- a/public/app/features/search/components/DashboardSearch.test.tsx +++ b/public/app/features/search/components/DashboardSearch.test.tsx @@ -1,15 +1,17 @@ -import React from 'react'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; import { locationService, setEchoSrv } from '@grafana/runtime'; import { selectOptionInTest } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; - -import * as SearchSrv from 'app/core/services/search_srv'; import * as MockSearchSrv from 'app/core/services/__mocks__/search_srv'; -import { DashboardSearch, Props } from './DashboardSearch'; +import { Echo } from 'app/core/services/echo/Echo'; +import * as SearchSrv from 'app/core/services/search_srv'; + import { searchResults } from '../testData'; import { SearchLayout } from '../types'; -import { Echo } from 'app/core/services/echo/Echo'; + +import { DashboardSearch, Props } from './DashboardSearch'; jest.mock('app/core/services/search_srv'); // Typecast the mock search so the mock import is correctly recognised by TS diff --git a/public/app/features/search/components/DashboardSearch.tsx b/public/app/features/search/components/DashboardSearch.tsx index 1864459e715..da60871d749 100644 --- a/public/app/features/search/components/DashboardSearch.tsx +++ b/public/app/features/search/components/DashboardSearch.tsx @@ -1,13 +1,16 @@ -import React, { FC, memo } from 'react'; import { css } from '@emotion/css'; -import { CustomScrollbar, IconButton, stylesFactory, useTheme2 } from '@grafana/ui'; +import React, { FC, memo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { useSearchQuery } from '../hooks/useSearchQuery'; +import { CustomScrollbar, IconButton, stylesFactory, useTheme2 } from '@grafana/ui'; + import { useDashboardSearch } from '../hooks/useDashboardSearch'; -import { SearchField } from './SearchField'; -import { SearchResults } from './SearchResults'; +import { useSearchQuery } from '../hooks/useSearchQuery'; + import { ActionRow } from './ActionRow'; import { PreviewsSystemRequirements } from './PreviewsSystemRequirements'; +import { SearchField } from './SearchField'; +import { SearchResults } from './SearchResults'; export interface Props { onCloseSearch: () => void; diff --git a/public/app/features/search/components/ManageDashboards.tsx b/public/app/features/search/components/ManageDashboards.tsx index 6f3b46b7c3b..0a5737ae657 100644 --- a/public/app/features/search/components/ManageDashboards.tsx +++ b/public/app/features/search/components/ManageDashboards.tsx @@ -1,18 +1,21 @@ -import React, { FC, memo, useState } from 'react'; import { css } from '@emotion/css'; -import { FilterInput, Spinner, stylesFactory, useTheme } from '@grafana/ui'; +import React, { FC, memo, useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { contextSrv } from 'app/core/services/context_srv'; +import { FilterInput, Spinner, stylesFactory, useTheme } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { contextSrv } from 'app/core/services/context_srv'; import { FolderDTO } from 'app/types'; + import { useManageDashboards } from '../hooks/useManageDashboards'; -import { SearchLayout } from '../types'; -import { ConfirmDeleteModal } from './ConfirmDeleteModal'; -import { MoveToFolderModal } from './MoveToFolderModal'; import { useSearchQuery } from '../hooks/useSearchQuery'; -import { SearchResultsFilter } from './SearchResultsFilter'; -import { SearchResults } from './SearchResults'; +import { SearchLayout } from '../types'; + +import { ConfirmDeleteModal } from './ConfirmDeleteModal'; import { DashboardActions } from './DashboardActions'; +import { MoveToFolderModal } from './MoveToFolderModal'; +import { SearchResults } from './SearchResults'; +import { SearchResultsFilter } from './SearchResultsFilter'; export interface Props { folder?: FolderDTO; diff --git a/public/app/features/search/components/MoveToFolderModal.tsx b/public/app/features/search/components/MoveToFolderModal.tsx index dac46e5ae31..b850f9462ba 100644 --- a/public/app/features/search/components/MoveToFolderModal.tsx +++ b/public/app/features/search/components/MoveToFolderModal.tsx @@ -1,13 +1,15 @@ -import React, { FC, useState } from 'react'; import { css } from '@emotion/css'; -import { Button, HorizontalGroup, Modal, stylesFactory, useTheme } from '@grafana/ui'; +import React, { FC, useState } from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { FolderInfo } from 'app/types'; +import { Button, HorizontalGroup, Modal, stylesFactory, useTheme } from '@grafana/ui'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import { useAppNotification } from 'app/core/copy/appNotification'; +import { moveDashboards } from 'app/features/manage-dashboards/state/actions'; +import { FolderInfo } from 'app/types'; + import { DashboardSection, OnMoveItems } from '../types'; import { getCheckedDashboards } from '../utils'; -import { moveDashboards } from 'app/features/manage-dashboards/state/actions'; interface Props { onMoveItems: OnMoveItems; diff --git a/public/app/features/search/components/PreviewsSystemRequirements.tsx b/public/app/features/search/components/PreviewsSystemRequirements.tsx index 0e023c4e138..c3ba284c01b 100644 --- a/public/app/features/search/components/PreviewsSystemRequirements.tsx +++ b/public/app/features/search/components/PreviewsSystemRequirements.tsx @@ -1,7 +1,8 @@ -import { Alert, useStyles2 } from '@grafana/ui'; -import React from 'react'; -import { config } from '@grafana/runtime/src'; import { css } from '@emotion/css'; +import React from 'react'; + +import { config } from '@grafana/runtime/src'; +import { Alert, useStyles2 } from '@grafana/ui'; export interface Props { showPreviews?: boolean; diff --git a/public/app/features/search/components/SearchCard.tsx b/public/app/features/search/components/SearchCard.tsx index 12a2d3e55a8..862c3deefc8 100644 --- a/public/app/features/search/components/SearchCard.tsx +++ b/public/app/features/search/components/SearchCard.tsx @@ -1,13 +1,16 @@ -import React, { useCallback, useRef, useState } from 'react'; import { css } from '@emotion/css'; +import React, { useCallback, useRef, useState } from 'react'; import { usePopper } from 'react-popper'; + import { GrafanaTheme2 } from '@grafana/data'; -import { Icon, Portal, TagList, useTheme2 } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; +import { Icon, Portal, TagList, useTheme2 } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; + import { DashboardSectionItem, OnToggleChecked } from '../types'; -import { SearchCheckbox } from './SearchCheckbox'; + import { SearchCardExpanded } from './SearchCardExpanded'; +import { SearchCheckbox } from './SearchCheckbox'; const DELAY_BEFORE_EXPANDING = 500; diff --git a/public/app/features/search/components/SearchCardExpanded.tsx b/public/app/features/search/components/SearchCardExpanded.tsx index 350e2b54347..548cce70706 100644 --- a/public/app/features/search/components/SearchCardExpanded.tsx +++ b/public/app/features/search/components/SearchCardExpanded.tsx @@ -1,9 +1,12 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; import classNames from 'classnames'; +import React, { useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Icon, Spinner, TagList, useTheme2 } from '@grafana/ui'; + import { DashboardSectionItem } from '../types'; + import { getThumbnailURL } from './SearchCard'; export interface Props { diff --git a/public/app/features/search/components/SearchCheckbox.tsx b/public/app/features/search/components/SearchCheckbox.tsx index a06703bfa28..d4c669aed49 100644 --- a/public/app/features/search/components/SearchCheckbox.tsx +++ b/public/app/features/search/components/SearchCheckbox.tsx @@ -1,4 +1,5 @@ import React, { FC, memo } from 'react'; + import { Checkbox } from '@grafana/ui'; interface Props { diff --git a/public/app/features/search/components/SearchField.tsx b/public/app/features/search/components/SearchField.tsx index 17272480c08..274fa528a31 100644 --- a/public/app/features/search/components/SearchField.tsx +++ b/public/app/features/search/components/SearchField.tsx @@ -1,9 +1,11 @@ -import React, { FC } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; -import { DashboardQuery } from '../types'; import { useStyles } from '@grafana/ui'; +import { DashboardQuery } from '../types'; + type Omit = Pick>; interface SearchFieldProps extends Omit, 'onChange'> { diff --git a/public/app/features/search/components/SearchItem.test.tsx b/public/app/features/search/components/SearchItem.test.tsx index 7e9744e1400..822b3c14c13 100644 --- a/public/app/features/search/components/SearchItem.test.tsx +++ b/public/app/features/search/components/SearchItem.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; -import { Props, SearchItem } from './SearchItem'; import { DashboardSearchItemType } from '../types'; +import { Props, SearchItem } from './SearchItem'; + beforeEach(() => { jest.clearAllMocks(); }); diff --git a/public/app/features/search/components/SearchItem.tsx b/public/app/features/search/components/SearchItem.tsx index 920bd52e1f8..d2eefa9654d 100644 --- a/public/app/features/search/components/SearchItem.tsx +++ b/public/app/features/search/components/SearchItem.tsx @@ -1,11 +1,14 @@ -import React, { FC, useCallback } from 'react'; import { css } from '@emotion/css'; +import React, { FC, useCallback } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { Card, Icon, IconName, TagList, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import { DashboardSectionItem, OnToggleChecked } from '../types'; -import { SearchCheckbox } from './SearchCheckbox'; + import { SEARCH_ITEM_HEIGHT } from '../constants'; +import { DashboardSectionItem, OnToggleChecked } from '../types'; + +import { SearchCheckbox } from './SearchCheckbox'; export interface Props { item: DashboardSectionItem; diff --git a/public/app/features/search/components/SearchResults.test.tsx b/public/app/features/search/components/SearchResults.test.tsx index ba249df1811..2b307496b25 100644 --- a/public/app/features/search/components/SearchResults.test.tsx +++ b/public/app/features/search/components/SearchResults.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectors } from '@grafana/e2e-selectors'; -import { Props, SearchResults } from './SearchResults'; import { generalFolder, searchResults } from '../testData'; import { SearchLayout } from '../types'; +import { Props, SearchResults } from './SearchResults'; + beforeEach(() => { jest.clearAllMocks(); }); diff --git a/public/app/features/search/components/SearchResults.tsx b/public/app/features/search/components/SearchResults.tsx index bc5da39620f..cafddc351c3 100644 --- a/public/app/features/search/components/SearchResults.tsx +++ b/public/app/features/search/components/SearchResults.tsx @@ -1,14 +1,17 @@ -import React, { FC, memo } from 'react'; import { css, cx } from '@emotion/css'; -import { FixedSizeList, FixedSizeGrid } from 'react-window'; +import React, { FC, memo } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; +import { FixedSizeList, FixedSizeGrid } from 'react-window'; + import { GrafanaTheme } from '@grafana/data'; -import { Spinner, stylesFactory, useTheme } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { DashboardSection, OnToggleChecked, SearchLayout } from '../types'; +import { Spinner, stylesFactory, useTheme } from '@grafana/ui'; + import { SEARCH_ITEM_HEIGHT, SEARCH_ITEM_MARGIN } from '../constants'; -import { SearchItem } from './SearchItem'; +import { DashboardSection, OnToggleChecked, SearchLayout } from '../types'; + import { SearchCard } from './SearchCard'; +import { SearchItem } from './SearchItem'; import { SectionHeader } from './SectionHeader'; export interface Props { diff --git a/public/app/features/search/components/SearchResultsFilter.test.tsx b/public/app/features/search/components/SearchResultsFilter.test.tsx index e88ce063da2..991e0d8bf53 100644 --- a/public/app/features/search/components/SearchResultsFilter.test.tsx +++ b/public/app/features/search/components/SearchResultsFilter.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectOptionInTest } from '@grafana/ui'; -import { Props, SearchResultsFilter } from './SearchResultsFilter'; + import { SearchLayout } from '../types'; +import { Props, SearchResultsFilter } from './SearchResultsFilter'; + jest.mock('app/core/services/search_srv'); const noop = jest.fn(); diff --git a/public/app/features/search/components/SearchResultsFilter.tsx b/public/app/features/search/components/SearchResultsFilter.tsx index 7a8b43b9ba7..51ec8d7ffcf 100644 --- a/public/app/features/search/components/SearchResultsFilter.tsx +++ b/public/app/features/search/components/SearchResultsFilter.tsx @@ -1,8 +1,11 @@ -import React, { FC, FormEvent } from 'react'; import { css } from '@emotion/css'; -import { Button, Checkbox, stylesFactory, useTheme, HorizontalGroup } from '@grafana/ui'; +import React, { FC, FormEvent } from 'react'; + import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { Button, Checkbox, stylesFactory, useTheme, HorizontalGroup } from '@grafana/ui'; + import { DashboardQuery, SearchLayout } from '../types'; + import { ActionRow } from './ActionRow'; import { PreviewsSystemRequirements } from './PreviewsSystemRequirements'; diff --git a/public/app/features/search/components/SearchWrapper.tsx b/public/app/features/search/components/SearchWrapper.tsx index b98771fdc21..506f8944ef9 100644 --- a/public/app/features/search/components/SearchWrapper.tsx +++ b/public/app/features/search/components/SearchWrapper.tsx @@ -1,8 +1,11 @@ import React, { FC, memo } from 'react'; -import DashboardSearch from './DashboardSearch'; + import { useUrlParams } from 'app/core/navigation/hooks'; + import { defaultQueryParams } from '../reducers/searchQueryReducer'; +import DashboardSearch from './DashboardSearch'; + export const SearchWrapper: FC = memo(() => { const [params, updateUrlParams] = useUrlParams(); const isOpen = params.get('search') === 'open'; diff --git a/public/app/features/search/components/SectionHeader.tsx b/public/app/features/search/components/SectionHeader.tsx index 685babe2379..43b418985a3 100644 --- a/public/app/features/search/components/SectionHeader.tsx +++ b/public/app/features/search/components/SectionHeader.tsx @@ -1,13 +1,15 @@ -import React, { FC, useCallback } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, useCallback } from 'react'; import { useLocalStorage } from 'react-use'; + import { GrafanaTheme } from '@grafana/data'; import { CollapsableSection, Icon, stylesFactory, useTheme } from '@grafana/ui'; +import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; import { DashboardSection, OnToggleChecked } from '../types'; -import { SearchCheckbox } from './SearchCheckbox'; import { getSectionIcon, getSectionStorageKey } from '../utils'; -import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; + +import { SearchCheckbox } from './SearchCheckbox'; interface SectionHeaderProps { editable?: boolean; diff --git a/public/app/features/search/hooks/useDashboardSearch.ts b/public/app/features/search/hooks/useDashboardSearch.ts index c3b7434f002..96267739453 100644 --- a/public/app/features/search/hooks/useDashboardSearch.ts +++ b/public/app/features/search/hooks/useDashboardSearch.ts @@ -1,14 +1,17 @@ import { KeyboardEvent, useReducer } from 'react'; +import { useDebounce } from 'react-use'; + +import { locationUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { DashboardQuery, DashboardSearchItemType, DashboardSection } from '../types'; + import { MOVE_SELECTION_DOWN, MOVE_SELECTION_UP } from '../reducers/actionTypes'; import { dashboardsSearchState, DashboardsSearchState, searchReducer } from '../reducers/dashboardSearch'; +import { DashboardQuery, DashboardSearchItemType, DashboardSection } from '../types'; import { findSelected } from '../utils'; -import { useSearch } from './useSearch'; -import { locationUtil } from '@grafana/data'; -import { useShowDashboardPreviews } from './useShowDashboardPreviews'; + import { reportDashboardListViewed } from './useManageDashboards'; -import { useDebounce } from 'react-use'; +import { useSearch } from './useSearch'; +import { useShowDashboardPreviews } from './useShowDashboardPreviews'; export const useDashboardSearch = (query: DashboardQuery, onCloseSearch: () => void) => { const reducer = useReducer(searchReducer, dashboardsSearchState); diff --git a/public/app/features/search/hooks/useManageDashboards.test.ts b/public/app/features/search/hooks/useManageDashboards.test.ts index a75c9957e1a..6c2dd608b0b 100644 --- a/public/app/features/search/hooks/useManageDashboards.test.ts +++ b/public/app/features/search/hooks/useManageDashboards.test.ts @@ -1,14 +1,16 @@ -import { Dispatch } from 'react'; import { renderHook } from '@testing-library/react-hooks'; +import { Dispatch } from 'react'; -import * as useSearch from './useSearch'; -import { DashboardQuery, DashboardSearchItemType, DashboardSection, SearchAction } from '../types'; -import { ManageDashboardsState } from '../reducers/manageDashboards'; -import { useManageDashboards } from './useManageDashboards'; -import { GENERAL_FOLDER_ID } from '../constants'; import { setEchoSrv } from '@grafana/runtime/src'; import { Echo } from 'app/core/services/echo/Echo'; +import { GENERAL_FOLDER_ID } from '../constants'; +import { ManageDashboardsState } from '../reducers/manageDashboards'; +import { DashboardQuery, DashboardSearchItemType, DashboardSection, SearchAction } from '../types'; + +import { useManageDashboards } from './useManageDashboards'; +import * as useSearch from './useSearch'; + describe('useManageDashboards', () => { const useSearchMock = jest.spyOn(useSearch, 'useSearch'); const toggle = async (section: DashboardSection) => section; diff --git a/public/app/features/search/hooks/useManageDashboards.ts b/public/app/features/search/hooks/useManageDashboards.ts index caa971f5b8f..bf55d4ff11f 100644 --- a/public/app/features/search/hooks/useManageDashboards.ts +++ b/public/app/features/search/hooks/useManageDashboards.ts @@ -1,14 +1,17 @@ import { useCallback, useMemo, useReducer } from 'react'; -import { FolderDTO } from 'app/types'; +import { useDebounce } from 'react-use'; + +import { reportInteraction } from '@grafana/runtime/src'; import { contextSrv } from 'app/core/services/context_srv'; -import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked, SearchLayout } from '../types'; +import { FolderDTO } from 'app/types'; + +import { GENERAL_FOLDER_ID } from '../constants'; import { DELETE_ITEMS, MOVE_ITEMS, TOGGLE_ALL_CHECKED, TOGGLE_CHECKED } from '../reducers/actionTypes'; import { manageDashboardsReducer, manageDashboardsState, ManageDashboardsState } from '../reducers/manageDashboards'; +import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked, SearchLayout } from '../types'; + import { useSearch } from './useSearch'; -import { GENERAL_FOLDER_ID } from '../constants'; import { useShowDashboardPreviews } from './useShowDashboardPreviews'; -import { reportInteraction } from '@grafana/runtime/src'; -import { useDebounce } from 'react-use'; const hasChecked = (section: DashboardSection) => { return section.checked || section.items.some((item) => item.checked); diff --git a/public/app/features/search/hooks/useSearch.ts b/public/app/features/search/hooks/useSearch.ts index cc138226294..fda96885d65 100644 --- a/public/app/features/search/hooks/useSearch.ts +++ b/public/app/features/search/hooks/useSearch.ts @@ -1,7 +1,9 @@ import { useCallback, useEffect } from 'react'; import { useDebounce } from 'react-use'; -import { SearchSrv } from 'app/core/services/search_srv'; + import { backendSrv } from 'app/core/services/backend_srv'; +import { SearchSrv } from 'app/core/services/search_srv'; + import { FETCH_RESULTS, FETCH_ITEMS, TOGGLE_SECTION, SEARCH_START, FETCH_ITEMS_START } from '../reducers/actionTypes'; import { DashboardSection, UseSearch } from '../types'; import { hasId, getParsedQuery } from '../utils'; diff --git a/public/app/features/search/hooks/useSearchQuery.ts b/public/app/features/search/hooks/useSearchQuery.ts index 64ec9e8b075..9b425e1066d 100644 --- a/public/app/features/search/hooks/useSearchQuery.ts +++ b/public/app/features/search/hooks/useSearchQuery.ts @@ -1,8 +1,9 @@ -import { FormEvent, useCallback, useReducer } from 'react'; import { debounce } from 'lodash'; +import { FormEvent, useCallback, useReducer } from 'react'; + import { SelectableValue } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { defaultQuery, defaultQueryParams, queryReducer } from '../reducers/searchQueryReducer'; + import { ADD_TAG, CLEAR_FILTERS, @@ -13,6 +14,7 @@ import { TOGGLE_STARRED, DATASOURCE_CHANGE, } from '../reducers/actionTypes'; +import { defaultQuery, defaultQueryParams, queryReducer } from '../reducers/searchQueryReducer'; import { DashboardQuery, SearchLayout } from '../types'; import { hasFilters, parseRouteParams } from '../utils'; diff --git a/public/app/features/search/hooks/useShowDashboardPreviews.ts b/public/app/features/search/hooks/useShowDashboardPreviews.ts index e15780fff6d..80e50f37e7b 100644 --- a/public/app/features/search/hooks/useShowDashboardPreviews.ts +++ b/public/app/features/search/hooks/useShowDashboardPreviews.ts @@ -1,10 +1,12 @@ -import { PREVIEWS_LOCAL_STORAGE_KEY } from '../constants'; -import { config } from '@grafana/runtime/src'; import { useLocalStorage } from 'react-use'; +import { config } from '@grafana/runtime/src'; + +import { PREVIEWS_LOCAL_STORAGE_KEY } from '../constants'; + export const useShowDashboardPreviews = () => { const previewFeatureEnabled = Boolean(config.featureToggles.dashboardPreviews); - const [showPreviews, setShowPreviews] = useLocalStorage(PREVIEWS_LOCAL_STORAGE_KEY, previewFeatureEnabled); + const [showPreviews, setShowPreviews] = useLocalStorage(PREVIEWS_LOCAL_STORAGE_KEY, false); return { showPreviews: Boolean(showPreviews && previewFeatureEnabled), previewFeatureEnabled, setShowPreviews }; }; diff --git a/public/app/features/search/loaders.ts b/public/app/features/search/loaders.ts index 98df76a95b6..0edaf223072 100644 --- a/public/app/features/search/loaders.ts +++ b/public/app/features/search/loaders.ts @@ -1,4 +1,5 @@ import { backendSrv } from 'app/core/services/backend_srv'; + import { buildNavModel } from '../folders/state/navModel'; export const loadFolderPage = (uid: string) => { diff --git a/public/app/features/search/page/SearchPage.tsx b/public/app/features/search/page/SearchPage.tsx index 923f677bc43..58712813895 100644 --- a/public/app/features/search/page/SearchPage.tsx +++ b/public/app/features/search/page/SearchPage.tsx @@ -1,17 +1,19 @@ -import React from 'react'; -import { GrafanaTheme2, NavModelItem } from '@grafana/data'; -import { Input, useStyles2, Spinner, Button } from '@grafana/ui'; -import { config } from '@grafana/runtime'; -import AutoSizer from 'react-virtualized-auto-sizer'; import { css } from '@emotion/css'; - -import Page from 'app/core/components/Page/Page'; +import React from 'react'; import { useAsync } from 'react-use'; -import { getGrafanaSearcher, QueryFilters } from '../service'; -import { Table } from './table/Table'; +import AutoSizer from 'react-virtualized-auto-sizer'; + +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Input, useStyles2, Spinner, Button } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; import { TagFilter, TermCount } from 'app/core/components/TagFilter/TagFilter'; -import { getTermCounts } from '../service/backend'; + import { useSearchQuery } from '../hooks/useSearchQuery'; +import { getGrafanaSearcher, QueryFilters } from '../service'; +import { getTermCounts } from '../service/backend'; + +import { Table } from './table/Table'; const node: NavModelItem = { id: 'search', diff --git a/public/app/features/search/page/table/Table.tsx b/public/app/features/search/page/table/Table.tsx index e28e17fa222..ab3afd8918e 100644 --- a/public/app/features/search/page/table/Table.tsx +++ b/public/app/features/search/page/table/Table.tsx @@ -1,13 +1,15 @@ +import { css } from '@emotion/css'; import React, { useMemo } from 'react'; import { useTable, Column, TableOptions, Cell, useAbsoluteLayout } from 'react-table'; -import { DataFrame, DataFrameType, DataFrameView, DataSourceRef, Field, GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; import { FixedSizeList } from 'react-window'; + +import { DataFrame, DataFrameType, DataFrameView, DataSourceRef, Field, GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; import { TableCell } from '@grafana/ui/src/components/Table/TableCell'; import { getTableStyles } from '@grafana/ui/src/components/Table/styles'; -import { useStyles2 } from '@grafana/ui'; import { LocationInfo } from '../../service'; + import { generateColumns } from './columns'; type Props = { diff --git a/public/app/features/search/page/table/columns.tsx b/public/app/features/search/page/table/columns.tsx index 5edab606248..7c65b721ee3 100644 --- a/public/app/features/search/page/table/columns.tsx +++ b/public/app/features/search/page/table/columns.tsx @@ -1,13 +1,15 @@ import React from 'react'; +import SVG from 'react-inlinesvg'; + import { DataFrameView, DataSourceRef, Field } from '@grafana/data'; import { config, getDataSourceSrv } from '@grafana/runtime'; -import SVG from 'react-inlinesvg'; import { Checkbox, Icon, IconName, TagList } from '@grafana/ui'; import { DefaultCell } from '@grafana/ui/src/components/Table/DefaultCell'; -import { FieldAccess, TableColumn } from './Table'; import { LocationInfo } from '../../service'; +import { FieldAccess, TableColumn } from './Table'; + export const generateColumns = ( data: DataFrameView, isDashboardList: boolean, diff --git a/public/app/features/search/reducers/dashboardSearch.test.ts b/public/app/features/search/reducers/dashboardSearch.test.ts index fc47dfd5435..5f5ebf182ec 100644 --- a/public/app/features/search/reducers/dashboardSearch.test.ts +++ b/public/app/features/search/reducers/dashboardSearch.test.ts @@ -1,6 +1,7 @@ +import { searchResults, sections } from '../testData'; + import { FETCH_ITEMS, FETCH_RESULTS, TOGGLE_SECTION, MOVE_SELECTION_DOWN, MOVE_SELECTION_UP } from './actionTypes'; import { searchReducer as reducer, dashboardsSearchState } from './dashboardSearch'; -import { searchResults, sections } from '../testData'; const defaultState = { selectedIndex: 0, loading: false, results: sections as any[], initialLoading: false }; describe('Dashboard Search reducer', () => { diff --git a/public/app/features/search/reducers/dashboardSearch.ts b/public/app/features/search/reducers/dashboardSearch.ts index 6c887960110..182b1ba4e5d 100644 --- a/public/app/features/search/reducers/dashboardSearch.ts +++ b/public/app/features/search/reducers/dashboardSearch.ts @@ -1,5 +1,6 @@ import { DashboardSection, SearchAction } from '../types'; import { getFlattenedSections, getLookupField, markSelected } from '../utils'; + import { FETCH_ITEMS, FETCH_RESULTS, diff --git a/public/app/features/search/reducers/manageDashboards.test.ts b/public/app/features/search/reducers/manageDashboards.test.ts index e4dfc41a0cb..a005996cf04 100644 --- a/public/app/features/search/reducers/manageDashboards.test.ts +++ b/public/app/features/search/reducers/manageDashboards.test.ts @@ -1,8 +1,9 @@ -import { TOGGLE_ALL_CHECKED, TOGGLE_CHECKED, DELETE_ITEMS, MOVE_ITEMS } from './actionTypes'; -import { manageDashboardsReducer as reducer, manageDashboardsState as state } from './manageDashboards'; import { sections } from '../testData'; import { DashboardSection, UidsToDelete } from '../types'; +import { TOGGLE_ALL_CHECKED, TOGGLE_CHECKED, DELETE_ITEMS, MOVE_ITEMS } from './actionTypes'; +import { manageDashboardsReducer as reducer, manageDashboardsState as state } from './manageDashboards'; + // Remove Recent and Starred sections as they're not used in manage dashboards const results = sections.slice(2); diff --git a/public/app/features/search/reducers/manageDashboards.ts b/public/app/features/search/reducers/manageDashboards.ts index f1467320124..ee58d2f422b 100644 --- a/public/app/features/search/reducers/manageDashboards.ts +++ b/public/app/features/search/reducers/manageDashboards.ts @@ -1,7 +1,8 @@ import { DashboardSection, DashboardSectionItem, SearchAction } from '../types'; +import { mergeReducers } from '../utils'; + import { TOGGLE_ALL_CHECKED, TOGGLE_CHECKED, MOVE_ITEMS, DELETE_ITEMS } from './actionTypes'; import { dashboardsSearchState, DashboardsSearchState, searchReducer } from './dashboardSearch'; -import { mergeReducers } from '../utils'; export interface ManageDashboardsState extends DashboardsSearchState { allChecked: boolean; diff --git a/public/app/features/search/reducers/searchQueryReducer.ts b/public/app/features/search/reducers/searchQueryReducer.ts index 6ba096ae266..9def0ce0de1 100644 --- a/public/app/features/search/reducers/searchQueryReducer.ts +++ b/public/app/features/search/reducers/searchQueryReducer.ts @@ -1,4 +1,5 @@ import { DashboardQuery, SearchQueryParams, SearchAction, SearchLayout } from '../types'; + import { ADD_TAG, CLEAR_FILTERS, diff --git a/public/app/features/search/service/backend.ts b/public/app/features/search/service/backend.ts index 34e4222f350..bd391ef95f7 100644 --- a/public/app/features/search/service/backend.ts +++ b/public/app/features/search/service/backend.ts @@ -1,3 +1,5 @@ +import { lastValueFrom } from 'rxjs'; + import { ArrayVector, DataFrame, @@ -9,11 +11,12 @@ import { Vector, } from '@grafana/data'; import { config, getDataSourceSrv } from '@grafana/runtime'; -import { GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; -import { lastValueFrom } from 'rxjs'; -import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { TermCount } from 'app/core/components/TagFilter/TagFilter'; +import { GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; +import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; + import { QueryFilters } from './types'; + import { QueryResult } from '.'; // The raw restuls from query server diff --git a/public/app/features/search/service/minisearcher.ts b/public/app/features/search/service/minisearcher.ts index 68db6a10b72..9a7bf738009 100644 --- a/public/app/features/search/service/minisearcher.ts +++ b/public/app/features/search/service/minisearcher.ts @@ -1,11 +1,13 @@ +import { isArray, isString } from 'lodash'; import MiniSearch from 'minisearch'; + import { ArrayVector, DataFrame, DataSourceRef, Field, FieldType, getDisplayProcessor, Vector } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { GrafanaSearcher, QueryFilters, QueryResponse } from './types'; import { filterFrame, getRawIndexData, RawIndexData, rawIndexSupplier } from './backend'; +import { GrafanaSearcher, QueryFilters, QueryResponse } from './types'; + import { LocationInfo } from '.'; -import { isArray, isString } from 'lodash'; export type SearchResultKind = keyof RawIndexData; diff --git a/public/app/features/search/types.ts b/public/app/features/search/types.ts index d7bcebd32da..cb657854ab0 100644 --- a/public/app/features/search/types.ts +++ b/public/app/features/search/types.ts @@ -1,6 +1,8 @@ import { Dispatch } from 'react'; import { Action } from 'redux'; + import { SelectableValue } from '@grafana/data'; + import { FolderInfo } from '../../types'; export enum DashboardSearchItemType { diff --git a/public/app/features/search/utils.test.ts b/public/app/features/search/utils.test.ts index 8f7f3a8af03..cbae358ab69 100644 --- a/public/app/features/search/utils.test.ts +++ b/public/app/features/search/utils.test.ts @@ -1,3 +1,5 @@ +import { sections, searchResults, checkedGeneralFolder, checkedOtherFolder, folderViewAllChecked } from './testData'; +import { SearchQueryParams } from './types'; import { findSelected, getCheckedDashboardsUids, @@ -7,8 +9,6 @@ import { mergeReducers, parseRouteParams, } from './utils'; -import { sections, searchResults, checkedGeneralFolder, checkedOtherFolder, folderViewAllChecked } from './testData'; -import { SearchQueryParams } from './types'; describe('Search utils', () => { describe('getFlattenedSections', () => { diff --git a/public/app/features/search/utils.ts b/public/app/features/search/utils.ts index 3d09fa56265..9d49cca2747 100644 --- a/public/app/features/search/utils.ts +++ b/public/app/features/search/utils.ts @@ -1,10 +1,13 @@ import { parse, SearchParserResult } from 'search-query-parser'; -import { IconName } from '@grafana/ui'; + import { UrlQueryMap } from '@grafana/data'; -import { DashboardQuery, DashboardSection, DashboardSectionItem, SearchAction, UidsToDelete } from './types'; -import { NO_ID_SECTIONS, SECTION_STORAGE_KEY } from './constants'; +import { IconName } from '@grafana/ui'; + import { getDashboardSrv } from '../dashboard/services/DashboardSrv'; +import { NO_ID_SECTIONS, SECTION_STORAGE_KEY } from './constants'; +import { DashboardQuery, DashboardSection, DashboardSectionItem, SearchAction, UidsToDelete } from './types'; + /** * Check if folder has id. Only Recent and Starred folders are the ones without * ids so far, as they are created manually after results are fetched from API. diff --git a/public/app/features/serviceaccounts/CreateServiceAccountTokenModal.tsx b/public/app/features/serviceaccounts/CreateServiceAccountTokenModal.tsx index 5cc322dc664..8823c862d8a 100644 --- a/public/app/features/serviceaccounts/CreateServiceAccountTokenModal.tsx +++ b/public/app/features/serviceaccounts/CreateServiceAccountTokenModal.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, diff --git a/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx b/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx index 2e58ee1faf4..ace8e467279 100644 --- a/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountCreatePage.tsx @@ -1,12 +1,14 @@ import React, { useCallback } from 'react'; import { connect } from 'react-redux'; -import { Form, Button, Input, Field } from '@grafana/ui'; +import { useHistory } from 'react-router-dom'; + import { NavModel } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; -import { StoreState } from '../../types'; -import { getNavModel } from '../../core/selectors/navModel'; +import { Form, Button, Input, Field } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; -import { useHistory } from 'react-router-dom'; + +import { getNavModel } from '../../core/selectors/navModel'; +import { StoreState } from '../../types'; interface ServiceAccountCreatePageProps { navModel: NavModel; diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.tsx index 061e7a0d309..4a5fb5f3963 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.tsx @@ -1,10 +1,17 @@ import React, { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { getNavModel } from 'app/core/selectors/navModel'; + +import { getTimeZone, NavModel } from '@grafana/data'; +import { Button } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; -import { ServiceAccountProfile } from './ServiceAccountProfile'; -import { StoreState, ServiceAccountDTO, ApiKey, Role, AccessControlAction } from 'app/types'; +import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { StoreState, ServiceAccountDTO, ApiKey, Role, AccessControlAction } from 'app/types'; + +import { CreateTokenModal, ServiceAccountToken } from './CreateServiceAccountTokenModal'; +import { ServiceAccountProfile } from './ServiceAccountProfile'; +import { ServiceAccountTokensTable } from './ServiceAccountTokensTable'; import { deleteServiceAccountToken, loadServiceAccount, @@ -14,11 +21,6 @@ import { updateServiceAccount, deleteServiceAccount, } from './state/actions'; -import { ServiceAccountTokensTable } from './ServiceAccountTokensTable'; -import { getTimeZone, NavModel } from '@grafana/data'; -import { Button } from '@grafana/ui'; -import { CreateTokenModal, ServiceAccountToken } from './CreateServiceAccountTokenModal'; -import { contextSrv } from 'app/core/core'; interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> { navModel: NavModel; diff --git a/public/app/features/serviceaccounts/ServiceAccountProfile.tsx b/public/app/features/serviceaccounts/ServiceAccountProfile.tsx index 368134f9077..e1ab18f7739 100644 --- a/public/app/features/serviceaccounts/ServiceAccountProfile.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountProfile.tsx @@ -1,10 +1,12 @@ -import React, { PureComponent, useRef, useState } from 'react'; -import { Role, ServiceAccountDTO, AccessControlAction } from 'app/types'; import { css, cx } from '@emotion/css'; +import React, { PureComponent, useRef, useState } from 'react'; + import { dateTimeFormat, GrafanaTheme2, OrgRole, TimeZone } from '@grafana/data'; import { Button, ConfirmButton, ConfirmModal, Input, LegacyInputStatus, useStyles2 } from '@grafana/ui'; -import { ServiceAccountRoleRow } from './ServiceAccountRoleRow'; import { contextSrv } from 'app/core/core'; +import { Role, ServiceAccountDTO, AccessControlAction } from 'app/types'; + +import { ServiceAccountRoleRow } from './ServiceAccountRoleRow'; interface Props { serviceAccount: ServiceAccountDTO; diff --git a/public/app/features/serviceaccounts/ServiceAccountRoleRow.tsx b/public/app/features/serviceaccounts/ServiceAccountRoleRow.tsx index 851d4b1298d..ea1d0eb6f57 100644 --- a/public/app/features/serviceaccounts/ServiceAccountRoleRow.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountRoleRow.tsx @@ -1,9 +1,11 @@ -import React, { PureComponent } from 'react'; import { css, cx } from '@emotion/css'; -import { AccessControlAction, OrgRole, Role, ServiceAccountDTO } from 'app/types'; -import { OrgRolePicker } from '../admin/OrgRolePicker'; -import { contextSrv } from 'app/core/core'; +import React, { PureComponent } from 'react'; + import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction, OrgRole, Role, ServiceAccountDTO } from 'app/types'; + +import { OrgRolePicker } from '../admin/OrgRolePicker'; interface Props { label: string; diff --git a/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx b/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx index e3c517bdef3..6d076ed8086 100644 --- a/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx @@ -1,11 +1,12 @@ -import React, { FC } from 'react'; -import { DeleteButton, Icon, Tooltip, useStyles2, useTheme2 } from '@grafana/ui'; -import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; - -import { AccessControlAction } from 'app/types'; -import { ApiKey } from '../../types'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; +import { DeleteButton, Icon, Tooltip, useStyles2, useTheme2 } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction } from 'app/types'; + +import { ApiKey } from '../../types'; interface Props { tokens: ApiKey[]; diff --git a/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx b/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx index b2d2d3aabb8..b1d7c8af80f 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx @@ -1,11 +1,14 @@ import { cx } from '@emotion/css'; +import React, { memo } from 'react'; + import { OrgRole } from '@grafana/data'; import { Button, Icon, useStyles2 } from '@grafana/ui'; import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; import { contextSrv } from 'app/core/core'; import { AccessControlAction, Role, ServiceAccountDTO } from 'app/types'; -import React, { memo } from 'react'; + import { OrgRolePicker } from '../admin/OrgRolePicker'; + import { getStyles } from './ServiceAccountsListPage'; type ServiceAccountListItemProps = { diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx index 2970193f335..f06120a2dbf 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.tsx @@ -1,10 +1,18 @@ +import { css, cx } from '@emotion/css'; +import pluralize from 'pluralize'; import React, { useEffect } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { ConfirmModal, FilterInput, LinkButton, RadioButtonGroup, useStyles2 } from '@grafana/ui'; -import { css, cx } from '@emotion/css'; +import { GrafanaTheme2, OrgRole } from '@grafana/data'; +import { ConfirmModal, FilterInput, LinkButton, RadioButtonGroup, useStyles2 } from '@grafana/ui'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import Page from 'app/core/components/Page/Page'; +import PageLoader from 'app/core/components/PageLoader/PageLoader'; +import { contextSrv } from 'app/core/core'; +import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState, ServiceAccountDTO, AccessControlAction } from 'app/types'; + +import ServiceAccountListItem from './ServiceAccountsListItem'; import { changeFilter, changeQuery, @@ -14,13 +22,6 @@ import { updateServiceAccount, setServiceAccountToRemove, } from './state/actions'; -import { getNavModel } from 'app/core/selectors/navModel'; -import PageLoader from 'app/core/components/PageLoader/PageLoader'; -import { GrafanaTheme2, OrgRole } from '@grafana/data'; -import { contextSrv } from 'app/core/core'; -import pluralize from 'pluralize'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import ServiceAccountListItem from './ServiceAccountsListItem'; interface OwnProps {} diff --git a/public/app/features/serviceaccounts/state/actions.ts b/public/app/features/serviceaccounts/state/actions.ts index 80380e5e492..9eb5ccb3d9b 100644 --- a/public/app/features/serviceaccounts/state/actions.ts +++ b/public/app/features/serviceaccounts/state/actions.ts @@ -1,5 +1,13 @@ -import { ServiceAccountDTO, ThunkResult, ServiceAccountFilter, AccessControlAction } from '../../../types'; +import { debounce } from 'lodash'; + import { getBackendSrv, locationService } from '@grafana/runtime'; +import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api'; +import { accessControlQueryParam } from 'app/core/utils/accessControl'; + +import { contextSrv } from '../../../core/services/context_srv'; +import { ServiceAccountDTO, ThunkResult, ServiceAccountFilter, AccessControlAction } from '../../../types'; +import { ServiceAccountToken } from '../CreateServiceAccountTokenModal'; + import { acOptionsLoaded, builtInRolesLoaded, @@ -13,11 +21,6 @@ import { serviceAccountTokensLoaded, serviceAccountToRemoveLoaded, } from './reducers'; -import { accessControlQueryParam } from 'app/core/utils/accessControl'; -import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api'; -import { debounce } from 'lodash'; -import { contextSrv } from '../../../core/services/context_srv'; -import { ServiceAccountToken } from '../CreateServiceAccountTokenModal'; const BASE_URL = `/api/serviceaccounts`; diff --git a/public/app/features/teams/CreateTeam.test.tsx b/public/app/features/teams/CreateTeam.test.tsx index c16c9d24c51..77af350c6aa 100644 --- a/public/app/features/teams/CreateTeam.test.tsx +++ b/public/app/features/teams/CreateTeam.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { CreateTeam, Props } from './CreateTeam'; describe('Render', () => { diff --git a/public/app/features/teams/CreateTeam.tsx b/public/app/features/teams/CreateTeam.tsx index 47373c38d76..7685e43c190 100644 --- a/public/app/features/teams/CreateTeam.tsx +++ b/public/app/features/teams/CreateTeam.tsx @@ -1,12 +1,13 @@ import React, { PureComponent } from 'react'; -import Page from 'app/core/components/Page/Page'; -import { Button, Form, Field, Input, FieldSet, Label, Tooltip, Icon } from '@grafana/ui'; +import { connect } from 'react-redux'; + import { NavModel } from '@grafana/data'; import { getBackendSrv, locationService } from '@grafana/runtime'; -import { connect } from 'react-redux'; +import { Button, Form, Field, Input, FieldSet, Label, Tooltip, Icon } from '@grafana/ui'; +import Page from 'app/core/components/Page/Page'; +import { contextSrv } from 'app/core/core'; import { getNavModel } from 'app/core/selectors/navModel'; import { StoreState } from 'app/types'; -import { contextSrv } from 'app/core/core'; export interface Props { navModel: NavModel; diff --git a/public/app/features/teams/TeamGroupSync.test.tsx b/public/app/features/teams/TeamGroupSync.test.tsx index 6ba6f050278..3d909176fa3 100644 --- a/public/app/features/teams/TeamGroupSync.test.tsx +++ b/public/app/features/teams/TeamGroupSync.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { Props, TeamGroupSync } from './TeamGroupSync'; +import React from 'react'; + import { TeamGroup } from '../../types'; + +import { Props, TeamGroupSync } from './TeamGroupSync'; import { getMockTeamGroups } from './__mocks__/teamMocks'; const setup = (propOverrides?: object) => { diff --git a/public/app/features/teams/TeamGroupSync.tsx b/public/app/features/teams/TeamGroupSync.tsx index a550b0cd9df..16531c2eea9 100644 --- a/public/app/features/teams/TeamGroupSync.tsx +++ b/public/app/features/teams/TeamGroupSync.tsx @@ -1,15 +1,19 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; +import { LegacyForms, Tooltip, Icon, Button, useTheme2 } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; -import { LegacyForms, Tooltip, Icon, Button } from '@grafana/ui'; -const { Input } = LegacyForms; +import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { UpgradeBox, UpgradeContent, UpgradeContentProps } from 'app/core/components/Upgrade/UpgradeBox'; +import { highlightTrial } from 'app/features/admin/utils'; import { StoreState, TeamGroup } from '../../types'; + import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions'; import { getTeamGroups } from './state/selectors'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; + +const { Input } = LegacyForms; function mapStateToProps(state: StoreState) { return { @@ -90,14 +94,25 @@ export class TeamGroupSync extends PureComponent { render() { const { isAdding, newGroupId } = this.state; const { groups, isReadOnly } = this.props; - return (
+ {highlightTrial() && ( + + )}
-

External group sync

- - - + {(!highlightTrial() || groups.length > 0) && ( + <> +

External group sync

+ + + + + )}
{groups.length > 0 && (
- {groups.length === 0 && !isAdding && ( - - )} + {groups.length === 0 && + !isAdding && + (highlightTrial() ? ( + + ) : ( + + ))} {groups.length > 0 && (
@@ -163,4 +182,22 @@ export class TeamGroupSync extends PureComponent { } } +export const TeamSyncUpgradeContent = ({ action }: { action?: UpgradeContentProps['action'] }) => { + const theme = useTheme2(); + return ( + + ); +}; export default connect(mapStateToProps, mapDispatchToProps)(TeamGroupSync); diff --git a/public/app/features/teams/TeamList.test.tsx b/public/app/features/teams/TeamList.test.tsx index aa53bf1d995..598ecdd3a5c 100644 --- a/public/app/features/teams/TeamList.test.tsx +++ b/public/app/features/teams/TeamList.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { Props, TeamList } from './TeamList'; -import { OrgRole, Team } from '../../types'; -import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks'; -import { contextSrv, User } from 'app/core/services/context_srv'; -import { NavModel } from '@grafana/data'; +import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + +import { NavModel } from '@grafana/data'; +import { contextSrv, User } from 'app/core/services/context_srv'; + +import { OrgRole, Team } from '../../types'; + +import { Props, TeamList } from './TeamList'; +import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks'; import { setSearchQuery, setTeamsSearchPage } from './state/reducers'; jest.mock('app/core/config', () => { diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index f044e4681d7..99913f21f52 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -1,18 +1,21 @@ import React, { PureComponent } from 'react'; -import Page from 'app/core/components/Page/Page'; -import { DeleteButton, LinkButton, FilterInput, VerticalGroup, HorizontalGroup, Pagination } from '@grafana/ui'; + import { NavModel } from '@grafana/data'; +import { DeleteButton, LinkButton, FilterInput, VerticalGroup, HorizontalGroup, Pagination } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import { AccessControlAction, Role, StoreState, Team } from 'app/types'; -import { deleteTeam, loadTeams } from './state/actions'; -import { getSearchQuery, getTeams, getTeamsCount, getTeamsSearchPage, isPermissionTeamAdmin } from './state/selectors'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { config } from 'app/core/config'; -import { contextSrv, User } from 'app/core/services/context_srv'; -import { connectWithCleanUp } from '../../core/components/connectWithCleanUp'; -import { setSearchQuery, setTeamsSearchPage } from './state/reducers'; +import Page from 'app/core/components/Page/Page'; import { TeamRolePicker } from 'app/core/components/RolePicker/TeamRolePicker'; import { fetchRoleOptions } from 'app/core/components/RolePicker/api'; +import { config } from 'app/core/config'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { contextSrv, User } from 'app/core/services/context_srv'; +import { AccessControlAction, Role, StoreState, Team } from 'app/types'; + +import { connectWithCleanUp } from '../../core/components/connectWithCleanUp'; + +import { deleteTeam, loadTeams } from './state/actions'; +import { setSearchQuery, setTeamsSearchPage } from './state/reducers'; +import { getSearchQuery, getTeams, getTeamsCount, getTeamsSearchPage, isPermissionTeamAdmin } from './state/selectors'; const pageLimit = 30; diff --git a/public/app/features/teams/TeamMemberRow.test.tsx b/public/app/features/teams/TeamMemberRow.test.tsx index f09d4d7742a..c2318e946fd 100644 --- a/public/app/features/teams/TeamMemberRow.test.tsx +++ b/public/app/features/teams/TeamMemberRow.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { TeamMember, TeamPermissionLevel } from '../../types'; -import { getMockTeamMember } from './__mocks__/teamMocks'; -import { TeamMemberRow, Props } from './TeamMemberRow'; +import React from 'react'; + import { SelectableValue } from '@grafana/data'; +import { TeamMember, TeamPermissionLevel } from '../../types'; + +import { TeamMemberRow, Props } from './TeamMemberRow'; +import { getMockTeamMember } from './__mocks__/teamMocks'; + const setup = (propOverrides?: object) => { const props: Props = { member: getMockTeamMember(), diff --git a/public/app/features/teams/TeamMemberRow.tsx b/public/app/features/teams/TeamMemberRow.tsx index 52c5229e25a..9d16ec471a3 100644 --- a/public/app/features/teams/TeamMemberRow.tsx +++ b/public/app/features/teams/TeamMemberRow.tsx @@ -1,13 +1,15 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { LegacyForms, DeleteButton } from '@grafana/ui'; -const { Select } = LegacyForms; -import { SelectableValue } from '@grafana/data'; -import { TeamMember, teamsPermissionLevels, TeamPermissionLevel } from 'app/types'; -import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle'; -import { updateTeamMember, removeTeamMember } from './state/actions'; +import { SelectableValue } from '@grafana/data'; +import { LegacyForms, DeleteButton } from '@grafana/ui'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; +import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle'; +import { TeamMember, teamsPermissionLevels, TeamPermissionLevel } from 'app/types'; + +import { updateTeamMember, removeTeamMember } from './state/actions'; + +const { Select } = LegacyForms; const mapDispatchToProps = { removeTeamMember, diff --git a/public/app/features/teams/TeamMembers.test.tsx b/public/app/features/teams/TeamMembers.test.tsx index fdd418ebd02..2253a4671cf 100644 --- a/public/app/features/teams/TeamMembers.test.tsx +++ b/public/app/features/teams/TeamMembers.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { Props, State, TeamMembers } from './TeamMembers'; -import { OrgRole, TeamMember } from '../../types'; -import { getMockTeamMembers } from './__mocks__/teamMocks'; -import { User } from 'app/core/services/context_srv'; +import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + +import { User } from 'app/core/services/context_srv'; + +import { OrgRole, TeamMember } from '../../types'; + +import { Props, State, TeamMembers } from './TeamMembers'; +import { getMockTeamMembers } from './__mocks__/teamMocks'; import { setSearchMemberQuery } from './state/reducers'; const signedInUserId = 1; diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index b505d9f296e..afa843d8b94 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -1,19 +1,21 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + +import { SelectableValue } from '@grafana/data'; +import { Button, FilterInput, Label } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; +import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import { UserPicker } from 'app/core/components/Select/UserPicker'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; -import { TeamMember, OrgUser } from 'app/types'; -import { addTeamMember } from './state/actions'; -import { getSearchMemberQuery, isSignedInUserTeamAdmin } from './state/selectors'; import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle'; import { config } from 'app/core/config'; import { contextSrv } from 'app/core/services/context_srv'; +import { TeamMember, OrgUser } from 'app/types'; + import TeamMemberRow from './TeamMemberRow'; +import { addTeamMember } from './state/actions'; import { setSearchMemberQuery } from './state/reducers'; -import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; -import { Button, FilterInput, Label } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; +import { getSearchMemberQuery, isSignedInUserTeamAdmin } from './state/selectors'; function mapStateToProps(state: any) { return { diff --git a/public/app/features/teams/TeamPages.test.tsx b/public/app/features/teams/TeamPages.test.tsx index 54eb487585c..91efcd8d58a 100644 --- a/public/app/features/teams/TeamPages.test.tsx +++ b/public/app/features/teams/TeamPages.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { Props, TeamPages } from './TeamPages'; -import { OrgRole, Team, TeamMember } from '../../types'; -import { getMockTeam } from './__mocks__/teamMocks'; -import { User } from 'app/core/services/context_srv'; +import React from 'react'; + import { NavModel, createTheme } from '@grafana/data'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { User } from 'app/core/services/context_srv'; + +import { OrgRole, Team, TeamMember } from '../../types'; + +import { Props, TeamPages } from './TeamPages'; +import { getMockTeam } from './__mocks__/teamMocks'; jest.mock('@grafana/runtime/src/config', () => ({ ...(jest.requireActual('@grafana/runtime/src/config') as unknown as object), diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index 685f7d1b5ac..f72b7f0b532 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -1,23 +1,25 @@ +import { includes } from 'lodash'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { includes } from 'lodash'; + +import { NavModel } from '@grafana/data'; +import { featureEnabled } from '@grafana/runtime'; import { Themeable2, withTheme2 } from '@grafana/ui'; -import config from 'app/core/config'; import Page from 'app/core/components/Page/Page'; +import { UpgradeBox } from 'app/core/components/Upgrade/UpgradeBox'; +import config from 'app/core/config'; +import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { contextSrv } from 'app/core/services/context_srv'; +import { AccessControlAction, StoreState } from 'app/types'; + +import TeamGroupSync, { TeamSyncUpgradeContent } from './TeamGroupSync'; import TeamMembers from './TeamMembers'; import TeamPermissions from './TeamPermissions'; import TeamSettings from './TeamSettings'; -import TeamGroupSync from './TeamGroupSync'; -import { AccessControlAction, StoreState } from 'app/types'; import { loadTeam, loadTeamMembers } from './state/actions'; -import { getTeam, getTeamMembers, isSignedInUserTeamAdmin } from './state/selectors'; import { getTeamLoadingNav } from './state/navModel'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { contextSrv } from 'app/core/services/context_srv'; -import { NavModel } from '@grafana/data'; -import { featureEnabled, reportExperimentView } from '@grafana/runtime'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import { UpgradeBox, UpgradeContent } from 'app/core/components/Upgrade/UpgradeBox'; +import { getTeam, getTeamMembers, isSignedInUserTeamAdmin } from './state/selectors'; interface TeamPageRouteParams { id: string; @@ -42,7 +44,7 @@ function mapStateToProps(state: StoreState, props: OwnProps) { const team = getTeam(state.team, teamId); let defaultPage = 'members'; if (contextSrv.accessControlEnabled()) { - // With FGAC the settings page will always be available + // With RBAC the settings page will always be available if (!team || !contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsPermissionsRead, team)) { defaultPage = 'settings'; } @@ -84,13 +86,6 @@ export class TeamPages extends PureComponent { async componentDidMount() { await this.fetchTeam(); - - const { isSyncEnabled } = this.state; - const currentPage = this.getCurrentPage(); - - if (currentPage === PageTypes.GroupSync && !isSyncEnabled && config.featureToggles.featureHighlights) { - reportExperimentView('feature-highlights-team-sync', 'test', ''); - } } async fetchTeam() { @@ -141,7 +136,7 @@ export class TeamPages extends PureComponent { renderPage(isSignedInUserTeamAdmin: boolean): React.ReactNode { const { isSyncEnabled } = this.state; - const { members, team, theme } = this.props; + const { members, team } = this.props; const currentPage = this.getCurrentPage(); const canReadTeam = contextSrv.hasAccessInMetadata( @@ -177,19 +172,8 @@ export class TeamPages extends PureComponent { } else if (config.featureToggles.featureHighlights) { return ( <> - - + + ); } diff --git a/public/app/features/teams/TeamPermissions.tsx b/public/app/features/teams/TeamPermissions.tsx index 6416bdd62ec..7dce1fef56f 100644 --- a/public/app/features/teams/TeamPermissions.tsx +++ b/public/app/features/teams/TeamPermissions.tsx @@ -1,8 +1,10 @@ import React from 'react'; + import { Permissions } from 'app/core/components/AccessControl'; -import { AccessControlAction, Team } from '../../types'; import { contextSrv } from 'app/core/services/context_srv'; +import { AccessControlAction, Team } from '../../types'; + type TeamPermissionsProps = { team: Team; }; diff --git a/public/app/features/teams/TeamSettings.test.tsx b/public/app/features/teams/TeamSettings.test.tsx index 4d4cfc0e83c..2b6bb754ca3 100644 --- a/public/app/features/teams/TeamSettings.test.tsx +++ b/public/app/features/teams/TeamSettings.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { Props, TeamSettings } from './TeamSettings'; import { getMockTeam } from './__mocks__/teamMocks'; diff --git a/public/app/features/teams/TeamSettings.tsx b/public/app/features/teams/TeamSettings.tsx index ede17d8da8f..1c0048fe910 100644 --- a/public/app/features/teams/TeamSettings.tsx +++ b/public/app/features/teams/TeamSettings.tsx @@ -1,11 +1,12 @@ import React, { FC } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { Input, Field, Form, Button, FieldSet, VerticalGroup } from '@grafana/ui'; +import { Input, Field, Form, Button, FieldSet, VerticalGroup } from '@grafana/ui'; import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences'; -import { updateTeam } from './state/actions'; -import { AccessControlAction, Team } from 'app/types'; import { contextSrv } from 'app/core/core'; +import { AccessControlAction, Team } from 'app/types'; + +import { updateTeam } from './state/actions'; const mapDispatchToProps = { updateTeam, diff --git a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap index 3abd26f7778..d9926d2bc57 100644 --- a/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap @@ -67,10 +67,10 @@ exports[`Render should render component 1`] = ` { return async (dispatch) => { diff --git a/public/app/features/teams/state/navModel.ts b/public/app/features/teams/state/navModel.ts index 9631b59f9b6..bdfb6a01231 100644 --- a/public/app/features/teams/state/navModel.ts +++ b/public/app/features/teams/state/navModel.ts @@ -1,9 +1,10 @@ -import { AccessControlAction, Team, TeamPermissionLevel } from 'app/types'; -import { featureEnabled } from '@grafana/runtime'; import { NavModelItem, NavModel } from '@grafana/data'; +import { featureEnabled } from '@grafana/runtime'; +import { ProBadge } from 'app/core/components/Upgrade/ProBadge'; import config from 'app/core/config'; import { contextSrv } from 'app/core/services/context_srv'; -import { ProBadge } from 'app/core/components/Upgrade/ProBadge'; +import { highlightTrial } from 'app/features/admin/utils'; +import { AccessControlAction, Team, TeamPermissionLevel } from 'app/types'; const loadingTeam = { avatarUrl: 'public/img/user_profile.png', @@ -23,7 +24,7 @@ export function buildNavModel(team: Team): NavModelItem { text: team.name, breadcrumbs: [{ title: 'Teams', url: 'org/teams' }], children: [ - // With FGAC this tab will always be available (but not always editable) + // With RBAC this tab will always be available (but not always editable) // With Legacy it will be hidden by hideTabsFromNonTeamAdmin should the user not be allowed to see it { active: false, @@ -36,7 +37,7 @@ export function buildNavModel(team: Team): NavModelItem { }; // While team is loading we leave the members tab - // With FGAC the Members tab is available when user has ActionTeamsPermissionsRead for this team + // With RBAC the Members tab is available when user has ActionTeamsPermissionsRead for this team // With Legacy it will always be present if ( team === loadingTeam || @@ -51,7 +52,7 @@ export function buildNavModel(team: Team): NavModelItem { }); } - const teamGroupSync = { + const teamGroupSync: NavModelItem = { active: false, icon: 'sync', id: `team-groupsync-${team.id}`, @@ -61,9 +62,14 @@ export function buildNavModel(team: Team): NavModelItem { const isLoadingTeam = team === loadingTeam; - // With both Legacy and FGAC the tab is protected being featureEnabled + if (highlightTrial()) { + teamGroupSync.tabSuffix = () => + ProBadge({ experimentId: isLoadingTeam ? '' : 'feature-highlights-team-sync-badge', eventVariant: 'trial' }); + } + + // With both Legacy and RBAC the tab is protected being featureEnabled // While team is loading we leave the teamsync tab - // With FGAC the External Group Sync tab is available when user has ActionTeamsPermissionsRead for this team + // With RBAC the External Group Sync tab is available when user has ActionTeamsPermissionsRead for this team if (featureEnabled('teamsync')) { if (isLoadingTeam || contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsPermissionsRead, team)) { navModel.children!.push(teamGroupSync); diff --git a/public/app/features/teams/state/reducers.test.ts b/public/app/features/teams/state/reducers.test.ts index 0dd9d83eaa6..5482f349650 100644 --- a/public/app/features/teams/state/reducers.test.ts +++ b/public/app/features/teams/state/reducers.test.ts @@ -1,3 +1,7 @@ +import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { TeamsState, TeamState } from '../../../types'; +import { getMockTeam, getMockTeamGroups, getMockTeamMember } from '../__mocks__/teamMocks'; + import { initialTeamsState, initialTeamState, @@ -10,9 +14,6 @@ import { teamsLoaded, teamsReducer, } from './reducers'; -import { getMockTeam, getMockTeamGroups, getMockTeamMember } from '../__mocks__/teamMocks'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { TeamsState, TeamState } from '../../../types'; describe('teams reducer', () => { describe('when teamsLoaded is dispatched', () => { diff --git a/public/app/features/teams/state/selectors.test.ts b/public/app/features/teams/state/selectors.test.ts index 6a5dd24117d..850943fb64b 100644 --- a/public/app/features/teams/state/selectors.test.ts +++ b/public/app/features/teams/state/selectors.test.ts @@ -1,8 +1,10 @@ -import { getTeam, getTeamMembers, getTeams, isSignedInUserTeamAdmin, Config } from './selectors'; -import { getMockTeam, getMockTeamMembers, getMultipleMockTeams } from '../__mocks__/teamMocks'; -import { Team, TeamGroup, TeamsState, TeamState, OrgRole } from '../../../types'; import { User } from 'app/core/services/context_srv'; +import { Team, TeamGroup, TeamsState, TeamState, OrgRole } from '../../../types'; +import { getMockTeam, getMockTeamMembers, getMultipleMockTeams } from '../__mocks__/teamMocks'; + +import { getTeam, getTeamMembers, getTeams, isSignedInUserTeamAdmin, Config } from './selectors'; + describe('Teams selectors', () => { describe('Get teams', () => { const mockTeams = getMultipleMockTeams(5); diff --git a/public/app/features/teams/state/selectors.ts b/public/app/features/teams/state/selectors.ts index f8461c7eb3a..a374645fd53 100644 --- a/public/app/features/teams/state/selectors.ts +++ b/public/app/features/teams/state/selectors.ts @@ -1,5 +1,5 @@ -import { Team, TeamsState, TeamState, TeamMember, OrgRole, TeamPermissionLevel } from 'app/types'; import { User } from 'app/core/services/context_srv'; +import { Team, TeamsState, TeamState, TeamMember, OrgRole, TeamPermissionLevel } from 'app/types'; export const getSearchQuery = (state: TeamsState) => state.searchQuery; export const getSearchMemberQuery = (state: TeamState) => state.searchMemberQuery; diff --git a/public/app/features/templating/formatRegistry.ts b/public/app/features/templating/formatRegistry.ts index 03e5f1bcd15..6256c4259ff 100644 --- a/public/app/features/templating/formatRegistry.ts +++ b/public/app/features/templating/formatRegistry.ts @@ -1,8 +1,10 @@ -import kbn from 'app/core/utils/kbn'; -import { dateTime, Registry, RegistryItem, textUtil, VariableModel } from '@grafana/data'; import { isArray, map, replace } from 'lodash'; -import { formatVariableLabel } from '../variables/shared/formatVariable'; + +import { dateTime, Registry, RegistryItem, textUtil, VariableModel } from '@grafana/data'; +import kbn from 'app/core/utils/kbn'; + import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../variables/constants'; +import { formatVariableLabel } from '../variables/shared/formatVariable'; export interface FormatOptions { value: any; diff --git a/public/app/features/templating/template_srv.mock.ts b/public/app/features/templating/template_srv.mock.ts index 4d4b4961de0..0d7e41f8ad4 100644 --- a/public/app/features/templating/template_srv.mock.ts +++ b/public/app/features/templating/template_srv.mock.ts @@ -1,7 +1,8 @@ import { ScopedVars, TimeRange, VariableModel } from '@grafana/data'; -import { variableRegex } from '../variables/utils'; import { TemplateSrv } from '@grafana/runtime'; +import { variableRegex } from '../variables/utils'; + /** * Mock for TemplateSrv where you can just supply map of key and values and it will do the interpolation based on that. * For simple tests whether you your data source for example calls correct replacing code. diff --git a/public/app/features/templating/template_srv.test.ts b/public/app/features/templating/template_srv.test.ts index c99d43b1a9f..25e9528ab35 100644 --- a/public/app/features/templating/template_srv.test.ts +++ b/public/app/features/templating/template_srv.test.ts @@ -1,13 +1,15 @@ import { dateTime, TimeRange } from '@grafana/data'; -import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv'; -import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput'; -import { VariableAdapter, variableAdapters } from '../variables/adapters'; -import { createQueryVariableAdapter } from '../variables/query/adapter'; -import { createAdHocVariableAdapter } from '../variables/adhoc/adapter'; -import { VariableModel } from '../variables/types'; -import { FormatRegistryID } from './formatRegistry'; import { setDataSourceSrv } from '@grafana/runtime'; + +import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput'; +import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv'; import { mockDataSource, MockDataSourceSrv } from '../alerting/unified/mocks'; +import { VariableAdapter, variableAdapters } from '../variables/adapters'; +import { createAdHocVariableAdapter } from '../variables/adhoc/adapter'; +import { createQueryVariableAdapter } from '../variables/query/adapter'; +import { VariableModel } from '../variables/types'; + +import { FormatRegistryID } from './formatRegistry'; const key = 'key'; diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index a1f024e4daa..7a06912ddfc 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -1,13 +1,16 @@ import { escape, isString, property } from 'lodash'; + import { deprecationWarning, ScopedVars, TimeRange } from '@grafana/data'; -import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors'; -import { variableRegex } from '../variables/utils'; -import { isAdHoc } from '../variables/guard'; -import { AdHocVariableFilter, AdHocVariableModel, VariableModel } from '../variables/types'; import { getDataSourceSrv, setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime'; -import { FormatOptions, formatRegistry, FormatRegistryID } from './formatRegistry'; + import { variableAdapters } from '../variables/adapters'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../variables/constants'; +import { isAdHoc } from '../variables/guard'; +import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors'; +import { AdHocVariableFilter, AdHocVariableModel, VariableModel } from '../variables/types'; +import { variableRegex } from '../variables/utils'; + +import { FormatOptions, formatRegistry, FormatRegistryID } from './formatRegistry'; interface FieldAccessorCache { [key: string]: (obj: any) => any; diff --git a/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx b/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx index 44fce104fbf..2afb51bd98a 100644 --- a/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx +++ b/public/app/features/transformers/FilterByValueTransformer/FilterByValueFilterEditor.tsx @@ -1,7 +1,9 @@ import React, { useCallback } from 'react'; -import { Button, Select } from '@grafana/ui'; + import { Field, SelectableValue, valueMatchers } from '@grafana/data'; import { FilterByValueFilter } from '@grafana/data/src/transformations/transformers/filterByValue'; +import { Button, Select } from '@grafana/ui'; + import { valueMatchersUI } from './ValueMatchers/valueMatchersUI'; interface Props { diff --git a/public/app/features/transformers/FilterByValueTransformer/FilterByValueTransformerEditor.tsx b/public/app/features/transformers/FilterByValueTransformer/FilterByValueTransformerEditor.tsx index 169d17dcd62..53f434ff5a1 100644 --- a/public/app/features/transformers/FilterByValueTransformer/FilterByValueTransformerEditor.tsx +++ b/public/app/features/transformers/FilterByValueTransformer/FilterByValueTransformerEditor.tsx @@ -1,5 +1,7 @@ -import React, { useMemo, useCallback } from 'react'; import { css } from '@emotion/css'; +import { cloneDeep } from 'lodash'; +import React, { useMemo, useCallback } from 'react'; + import { DataTransformerID, standardTransformers, @@ -12,14 +14,13 @@ import { ValueMatcherID, valueMatchers, } from '@grafana/data'; -import { Button, RadioButtonGroup, stylesFactory } from '@grafana/ui'; -import { cloneDeep } from 'lodash'; import { FilterByValueFilter, FilterByValueMatch, FilterByValueTransformerOptions, FilterByValueType, } from '@grafana/data/src/transformations/transformers/filterByValue'; +import { Button, RadioButtonGroup, stylesFactory } from '@grafana/ui'; import { DataFrameFieldsInfo, FilterByValueFilterEditor } from './FilterByValueFilterEditor'; diff --git a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx index 00499b90cc3..726c2fb8bbd 100644 --- a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx +++ b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx @@ -1,6 +1,8 @@ import React, { useCallback, useState } from 'react'; -import { Input } from '@grafana/ui'; + import { ValueMatcherID, BasicValueMatcherOptions } from '@grafana/data'; +import { Input } from '@grafana/ui'; + import { ValueMatcherEditorConfig, ValueMatcherUIProps, ValueMatcherUIRegistryItem } from './types'; import { convertToType } from './utils'; diff --git a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx index 84119754b0b..ea764d8dc3e 100644 --- a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx +++ b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx @@ -1,5 +1,7 @@ -import { ValueMatcherID } from '@grafana/data'; import React from 'react'; + +import { ValueMatcherID } from '@grafana/data'; + import { ValueMatcherUIProps, ValueMatcherUIRegistryItem } from './types'; export const NoopMatcherEditor: React.FC> = () => { diff --git a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx index 5738f38b09e..fc64c0b64a1 100644 --- a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx +++ b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx @@ -1,6 +1,8 @@ import React, { useCallback, useState } from 'react'; -import { Input } from '@grafana/ui'; + import { ValueMatcherID, RangeValueMatcherOptions } from '@grafana/data'; +import { Input } from '@grafana/ui'; + import { ValueMatcherEditorConfig, ValueMatcherUIProps, ValueMatcherUIRegistryItem } from './types'; import { convertToType } from './utils'; diff --git a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/utils.ts b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/utils.ts index f30f590a476..5c95ffe729b 100644 --- a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/utils.ts +++ b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/utils.ts @@ -1,6 +1,7 @@ -import { Field, FieldType } from '@grafana/data'; import { isString, isUndefined } from 'lodash'; +import { Field, FieldType } from '@grafana/data'; + export function convertToType(value: any, field: Field): any { switch (field.type) { case FieldType.boolean: diff --git a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts index 0517333aca6..d1c7e994c9e 100644 --- a/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts +++ b/public/app/features/transformers/FilterByValueTransformer/ValueMatchers/valueMatchersUI.ts @@ -1,4 +1,5 @@ import { Registry } from '@grafana/data'; + import { getBasicValueMatchersUI } from './BasicMatcherEditor'; import { getNoopValueMatchersUI } from './NoopMatcherEditor'; import { getRangeValueMatchersUI } from './RangeMatcherEditor'; diff --git a/public/app/features/transformers/calculateHeatmap/HeatmapTransformerEditor.tsx b/public/app/features/transformers/calculateHeatmap/HeatmapTransformerEditor.tsx index dc0e6eb1fed..aca854f71d3 100644 --- a/public/app/features/transformers/calculateHeatmap/HeatmapTransformerEditor.tsx +++ b/public/app/features/transformers/calculateHeatmap/HeatmapTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; + import { PanelOptionsEditorBuilder, PluginState, @@ -7,10 +8,11 @@ import { TransformerUIProps, } from '@grafana/data'; -import { HeatmapTransformerOptions, heatmapTransformer } from './heatmap'; -import { addHeatmapCalculationOptions } from './editor/helper'; import { getDefaultOptions, getTransformerOptionPane } from '../spatial/optionsHelper'; +import { addHeatmapCalculationOptions } from './editor/helper'; +import { HeatmapTransformerOptions, heatmapTransformer } from './heatmap'; + // Nothing defined in state const supplier = ( builder: PanelOptionsEditorBuilder, diff --git a/public/app/features/transformers/calculateHeatmap/editor/AxisEditor.tsx b/public/app/features/transformers/calculateHeatmap/editor/AxisEditor.tsx index a35e9ba126c..c459dba078a 100644 --- a/public/app/features/transformers/calculateHeatmap/editor/AxisEditor.tsx +++ b/public/app/features/transformers/calculateHeatmap/editor/AxisEditor.tsx @@ -1,6 +1,8 @@ import React from 'react'; + import { SelectableValue, StandardEditorProps } from '@grafana/data'; import { HorizontalGroup, Input, RadioButtonGroup } from '@grafana/ui'; + import { HeatmapCalculationAxisConfig, HeatmapCalculationMode } from '../models.gen'; const modeOptions: Array> = [ diff --git a/public/app/features/transformers/calculateHeatmap/editor/helper.ts b/public/app/features/transformers/calculateHeatmap/editor/helper.ts index 5ba5721f43d..af78c5113c6 100644 --- a/public/app/features/transformers/calculateHeatmap/editor/helper.ts +++ b/public/app/features/transformers/calculateHeatmap/editor/helper.ts @@ -1,6 +1,7 @@ import { PanelOptionsEditorBuilder } from '@grafana/data'; import { HeatmapCalculationMode, HeatmapCalculationOptions } from '../models.gen'; + import { AxisEditor } from './AxisEditor'; export function addHeatmapCalculationOptions( diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.test.ts b/public/app/features/transformers/calculateHeatmap/heatmap.test.ts index 58303f46cf5..d2302dcf2d3 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.test.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.test.ts @@ -1,5 +1,6 @@ import { FieldType } from '@grafana/data'; import { toDataFrame } from '@grafana/data/src/dataframe/processDataFrame'; + import { calculateHeatmapFromData } from './heatmap'; import { HeatmapCalculationOptions } from './models.gen'; diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.ts b/public/app/features/transformers/calculateHeatmap/heatmap.ts index a714be39029..5b7e3d8b8a6 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.ts @@ -1,3 +1,5 @@ +import { map } from 'rxjs'; + import { ArrayVector, DataFrame, @@ -10,7 +12,7 @@ import { getFieldDisplayName, Field, } from '@grafana/data'; -import { map } from 'rxjs'; + import { HeatmapCalculationMode, HeatmapCalculationOptions } from './models.gen'; import { niceLinearIncrs, niceTimeIncrs } from './utils'; diff --git a/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.test.tsx b/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.test.tsx index 863d0f0c0cc..d6c866c30d6 100644 --- a/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.test.tsx +++ b/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { toDataFrame, FieldType } from '@grafana/data'; import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; + +import { toDataFrame, FieldType } from '@grafana/data'; import { selectOptionInTest } from '@grafana/ui'; + import { Props, ConfigFromQueryTransformerEditor } from './ConfigFromQueryTransformerEditor'; beforeEach(() => { diff --git a/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx b/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx index 1a0a8303a44..081c87d8e00 100644 --- a/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx +++ b/public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx @@ -1,4 +1,6 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { FieldMatcherID, GrafanaTheme2, @@ -7,10 +9,11 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; -import { configFromDataTransformer, ConfigFromQueryTransformOptions } from './configFromQuery'; import { fieldMatchersUI, InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui'; + import { FieldToConfigMappingEditor } from '../fieldToConfigMapping/FieldToConfigMappingEditor'; -import { css } from '@emotion/css'; + +import { configFromDataTransformer, ConfigFromQueryTransformOptions } from './configFromQuery'; export interface Props extends TransformerUIProps {} diff --git a/public/app/features/transformers/configFromQuery/configFromQuery.test.ts b/public/app/features/transformers/configFromQuery/configFromQuery.test.ts index 99ecd9e9086..2e5266590c1 100644 --- a/public/app/features/transformers/configFromQuery/configFromQuery.test.ts +++ b/public/app/features/transformers/configFromQuery/configFromQuery.test.ts @@ -1,5 +1,7 @@ import { toDataFrame, FieldType, ReducerID } from '@grafana/data'; + import { FieldConfigHandlerKey } from '../fieldToConfigMapping/fieldToConfigMapping'; + import { extractConfigFromQuery, ConfigFromQueryTransformOptions } from './configFromQuery'; describe('config from data', () => { diff --git a/public/app/features/transformers/configFromQuery/configFromQuery.ts b/public/app/features/transformers/configFromQuery/configFromQuery.ts index 3f140f721d5..c114a2ce0d6 100644 --- a/public/app/features/transformers/configFromQuery/configFromQuery.ts +++ b/public/app/features/transformers/configFromQuery/configFromQuery.ts @@ -1,4 +1,5 @@ import { map } from 'rxjs/operators'; + import { ArrayVector, DataFrame, @@ -10,6 +11,7 @@ import { MatcherConfig, reduceField, } from '@grafana/data'; + import { getFieldConfigFromFrame, FieldToConfigMapping, diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx b/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx index 6f7c1d97e4c..7cb6abcf1a0 100644 --- a/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx +++ b/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx @@ -1,6 +1,8 @@ +import { defaults } from 'lodash'; import React, { ChangeEvent } from 'react'; import { of, OperatorFunction } from 'rxjs'; import { map } from 'rxjs/operators'; + import { BinaryOperationID, binaryOperators, @@ -15,7 +17,6 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; -import { FilterPill, HorizontalGroup, Input, LegacyForms, Select, StatsPicker } from '@grafana/ui'; import { BinaryOptions, CalculateFieldMode, @@ -23,8 +24,7 @@ import { getNameFromOptions, ReduceOptions, } from '@grafana/data/src/transformations/transformers/calculateField'; - -import { defaults } from 'lodash'; +import { FilterPill, HorizontalGroup, Input, LegacyForms, Select, StatsPicker } from '@grafana/ui'; interface CalculateFieldTransformerEditorProps extends TransformerUIProps {} diff --git a/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx b/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx index 0e7749234f0..fe60117bd59 100644 --- a/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { ChangeEvent } from 'react'; + import { DataTransformerID, SelectableValue, @@ -6,11 +7,11 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; -import { Input, Select } from '@grafana/ui'; import { ConcatenateFrameNameMode, ConcatenateTransformerOptions, } from '@grafana/data/src/transformations/transformers/concat'; +import { Input, Select } from '@grafana/ui'; interface ConcatenateTransformerEditorProps extends TransformerUIProps {} diff --git a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx index 456761e5c5c..7d32106224c 100644 --- a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { ChangeEvent, useCallback } from 'react'; + import { DataTransformerID, FieldNamePickerConfigSettings, @@ -9,11 +10,11 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; - import { ConvertFieldTypeTransformerOptions } from '@grafana/data/src/transformations/transformers/convertFieldType'; import { Button, InlineField, InlineFieldRow, Input, Select } from '@grafana/ui'; -import { FieldNamePicker } from '../../../../../packages/grafana-ui/src/components/MatchersUI/FieldNamePicker'; + import { ConvertFieldTypeOptions } from '../../../../../packages/grafana-data/src/transformations/transformers/convertFieldType'; +import { FieldNamePicker } from '../../../../../packages/grafana-ui/src/components/MatchersUI/FieldNamePicker'; const fieldNamePickerSettings: StandardEditorsRegistryItem = { settings: { width: 24 }, diff --git a/public/app/features/transformers/editors/FilterByNameTransformerEditor.tsx b/public/app/features/transformers/editors/FilterByNameTransformerEditor.tsx index dcf3ec3d12c..edb19aef24a 100644 --- a/public/app/features/transformers/editors/FilterByNameTransformerEditor.tsx +++ b/public/app/features/transformers/editors/FilterByNameTransformerEditor.tsx @@ -1,4 +1,6 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { DataTransformerID, KeyValue, @@ -8,9 +10,8 @@ import { getFieldDisplayName, stringToJsRegex, } from '@grafana/data'; -import { Field, Input, FilterPill, HorizontalGroup } from '@grafana/ui'; -import { css } from '@emotion/css'; import { FilterFieldsByNameTransformerOptions } from '@grafana/data/src/transformations/transformers/filterByName'; +import { Field, Input, FilterPill, HorizontalGroup } from '@grafana/ui'; interface FilterByNameTransformerEditorProps extends TransformerUIProps {} diff --git a/public/app/features/transformers/editors/FilterByRefIdTransformerEditor.tsx b/public/app/features/transformers/editors/FilterByRefIdTransformerEditor.tsx index f17536c3b58..036e8eb4bc8 100644 --- a/public/app/features/transformers/editors/FilterByRefIdTransformerEditor.tsx +++ b/public/app/features/transformers/editors/FilterByRefIdTransformerEditor.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataTransformerID, KeyValue, @@ -6,9 +7,8 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; -import { HorizontalGroup, FilterPill } from '@grafana/ui'; - import { FilterFramesByRefIdTransformerOptions } from '@grafana/data/src/transformations/transformers/filterByRefId'; +import { HorizontalGroup, FilterPill } from '@grafana/ui'; interface FilterByRefIdTransformerEditorProps extends TransformerUIProps {} diff --git a/public/app/features/transformers/editors/GroupByTransformerEditor.tsx b/public/app/features/transformers/editors/GroupByTransformerEditor.tsx index d6e6c372817..2812bf86848 100644 --- a/public/app/features/transformers/editors/GroupByTransformerEditor.tsx +++ b/public/app/features/transformers/editors/GroupByTransformerEditor.tsx @@ -1,5 +1,6 @@ -import React, { useCallback } from 'react'; import { css, cx } from '@emotion/css'; +import React, { useCallback } from 'react'; + import { DataTransformerID, ReducerID, @@ -8,13 +9,13 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; -import { Select, StatsPicker, stylesFactory } from '@grafana/ui'; - import { GroupByFieldOptions, GroupByOperationID, GroupByTransformerOptions, } from '@grafana/data/src/transformations/transformers/groupBy'; +import { Select, StatsPicker, stylesFactory } from '@grafana/ui'; + import { useAllFieldNamesFromDataFrames } from '../utils'; interface FieldProps { diff --git a/public/app/features/transformers/editors/GroupingToMatrixTransformerEditor.tsx b/public/app/features/transformers/editors/GroupingToMatrixTransformerEditor.tsx index 32cc311f61b..585493ce0ac 100644 --- a/public/app/features/transformers/editors/GroupingToMatrixTransformerEditor.tsx +++ b/public/app/features/transformers/editors/GroupingToMatrixTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { useCallback } from 'react'; + import { DataTransformerID, SelectableValue, @@ -8,6 +9,7 @@ import { GroupingToMatrixTransformerOptions, } from '@grafana/data'; import { InlineField, InlineFieldRow, Select } from '@grafana/ui'; + import { useAllFieldNamesFromDataFrames } from '../utils'; export const GroupingToMatrixTransformerEditor: React.FC> = ({ diff --git a/public/app/features/transformers/editors/HistogramTransformerEditor.tsx b/public/app/features/transformers/editors/HistogramTransformerEditor.tsx index 0b65a9fc8ae..82982aa8dee 100644 --- a/public/app/features/transformers/editors/HistogramTransformerEditor.tsx +++ b/public/app/features/transformers/editors/HistogramTransformerEditor.tsx @@ -1,6 +1,6 @@ import React, { FormEvent, useCallback } from 'react'; -import { DataTransformerID, standardTransformers, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; +import { DataTransformerID, standardTransformers, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; import { HistogramTransformerOptions, histogramFieldInfo, diff --git a/public/app/features/transformers/editors/LabelsToFieldsTransformerEditor.tsx b/public/app/features/transformers/editors/LabelsToFieldsTransformerEditor.tsx index 0101173bba1..a7f164cfe3d 100644 --- a/public/app/features/transformers/editors/LabelsToFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/editors/LabelsToFieldsTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; + import { DataTransformerID, SelectableValue, @@ -6,12 +7,11 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; -import { InlineField, InlineFieldRow, RadioButtonGroup, Select, FilterPill } from '@grafana/ui'; - import { LabelsToFieldsMode, LabelsToFieldsOptions, } from '@grafana/data/src/transformations/transformers/labelsToFields'; +import { InlineField, InlineFieldRow, RadioButtonGroup, Select, FilterPill } from '@grafana/ui'; const modes: Array> = [ { value: LabelsToFieldsMode.Columns, label: 'Columns' }, diff --git a/public/app/features/transformers/editors/MergeTransformerEditor.tsx b/public/app/features/transformers/editors/MergeTransformerEditor.tsx index c2905712d3e..69e86d4af89 100644 --- a/public/app/features/transformers/editors/MergeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/MergeTransformerEditor.tsx @@ -1,8 +1,10 @@ import React from 'react'; + import { DataTransformerID, standardTransformers, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; -import { MergeTransformerOptions } from '../../../../../packages/grafana-data/src/transformations/transformers/merge'; import { FieldValidationMessage } from '@grafana/ui'; +import { MergeTransformerOptions } from '../../../../../packages/grafana-data/src/transformations/transformers/merge'; + export const MergeTransformerEditor: React.FC> = ({ input, options, diff --git a/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx b/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx index 2f7c9a2e023..95eec6a6230 100644 --- a/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx @@ -1,6 +1,7 @@ -import React, { useCallback, useMemo } from 'react'; import { css } from '@emotion/css'; +import React, { useCallback, useMemo } from 'react'; import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; + import { DataTransformerID, GrafanaTheme, @@ -8,10 +9,10 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; +import { createOrderFieldsComparer } from '@grafana/data/src/transformations/transformers/order'; +import { OrganizeFieldsTransformerOptions } from '@grafana/data/src/transformations/transformers/organize'; import { stylesFactory, useTheme, Input, IconButton, Icon, FieldValidationMessage } from '@grafana/ui'; -import { OrganizeFieldsTransformerOptions } from '@grafana/data/src/transformations/transformers/organize'; -import { createOrderFieldsComparer } from '@grafana/data/src/transformations/transformers/order'; import { useAllFieldNamesFromDataFrames } from '../utils'; interface OrganizeFieldsTransformerEditorProps extends TransformerUIProps {} diff --git a/public/app/features/transformers/editors/ReduceTransformerEditor.tsx b/public/app/features/transformers/editors/ReduceTransformerEditor.tsx index 24be86576e0..188703a4d80 100644 --- a/public/app/features/transformers/editors/ReduceTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ReduceTransformerEditor.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { LegacyForms, Select, StatsPicker } from '@grafana/ui'; + import { DataTransformerID, ReducerID, @@ -8,9 +8,9 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; - import { ReduceTransformerMode, ReduceTransformerOptions } from '@grafana/data/src/transformations/transformers/reduce'; import { selectors } from '@grafana/e2e-selectors'; +import { LegacyForms, Select, StatsPicker } from '@grafana/ui'; // TODO: Minimal implementation, needs some <3 export const ReduceTransformerEditor: React.FC> = ({ diff --git a/public/app/features/transformers/editors/RenameByRegexTransformer.tsx b/public/app/features/transformers/editors/RenameByRegexTransformer.tsx index 23d16034d95..1fd61587cf0 100644 --- a/public/app/features/transformers/editors/RenameByRegexTransformer.tsx +++ b/public/app/features/transformers/editors/RenameByRegexTransformer.tsx @@ -1,4 +1,6 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { DataTransformerID, standardTransformers, @@ -6,9 +8,8 @@ import { TransformerUIProps, stringToJsRegex, } from '@grafana/data'; -import { Field, Input } from '@grafana/ui'; -import { css } from '@emotion/css'; import { RenameByRegexTransformerOptions } from '@grafana/data/src/transformations/transformers/renameByRegex'; +import { Field, Input } from '@grafana/ui'; interface RenameByRegexTransformerEditorProps extends TransformerUIProps {} diff --git a/public/app/features/transformers/editors/SeriesToFieldsTransformerEditor.tsx b/public/app/features/transformers/editors/SeriesToFieldsTransformerEditor.tsx index 028c9c55d1a..82915e7fbd3 100644 --- a/public/app/features/transformers/editors/SeriesToFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/editors/SeriesToFieldsTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { useCallback } from 'react'; + import { DataTransformerID, SelectableValue, @@ -6,9 +7,9 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; +import { SeriesToColumnsOptions } from '@grafana/data/src/transformations/transformers/seriesToColumns'; import { Select } from '@grafana/ui'; -import { SeriesToColumnsOptions } from '@grafana/data/src/transformations/transformers/seriesToColumns'; import { useAllFieldNamesFromDataFrames } from '../utils'; export const SeriesToFieldsTransformerEditor: React.FC> = ({ diff --git a/public/app/features/transformers/editors/SeriesToRowsTransformerEditor.tsx b/public/app/features/transformers/editors/SeriesToRowsTransformerEditor.tsx index 0355877f97c..01673a5bcda 100644 --- a/public/app/features/transformers/editors/SeriesToRowsTransformerEditor.tsx +++ b/public/app/features/transformers/editors/SeriesToRowsTransformerEditor.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataTransformerID, standardTransformers, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; import { SeriesToRowsTransformerOptions } from '@grafana/data/src/transformations/transformers/seriesToRows'; diff --git a/public/app/features/transformers/editors/SortByTransformerEditor.tsx b/public/app/features/transformers/editors/SortByTransformerEditor.tsx index 51c931fc1b0..f7b27f71f68 100644 --- a/public/app/features/transformers/editors/SortByTransformerEditor.tsx +++ b/public/app/features/transformers/editors/SortByTransformerEditor.tsx @@ -1,7 +1,9 @@ import React, { useCallback } from 'react'; + import { DataTransformerID, standardTransformers, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; -import { InlineField, InlineSwitch, InlineFieldRow, Select } from '@grafana/ui'; import { SortByField, SortByTransformerOptions } from '@grafana/data/src/transformations/transformers/sortBy'; +import { InlineField, InlineSwitch, InlineFieldRow, Select } from '@grafana/ui'; + import { useAllFieldNamesFromDataFrames } from '../utils'; export const SortByTransformerEditor: React.FC> = ({ diff --git a/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx b/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx index d7a868b7c2a..deff6aade82 100644 --- a/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/extractFields/ExtractFieldsTransformerEditor.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { DataTransformerID, FieldNamePickerConfigSettings, @@ -8,9 +9,9 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; - import { InlineField, InlineFieldRow, InlineSwitch, Select } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker'; + import { ExtractFieldsOptions, extractFieldsTransformer } from './extractFields'; import { FieldExtractorID, fieldExtractors } from './fieldExtractors'; diff --git a/public/app/features/transformers/extractFields/extractFields.test.ts b/public/app/features/transformers/extractFields/extractFields.test.ts index 753cd065950..a25d7b299d0 100644 --- a/public/app/features/transformers/extractFields/extractFields.test.ts +++ b/public/app/features/transformers/extractFields/extractFields.test.ts @@ -1,4 +1,5 @@ import { toDataFrame } from '@grafana/data/src/dataframe/processDataFrame'; + import { ExtractFieldsOptions, extractFieldsTransformer } from './extractFields'; describe('Fields from JSON', () => { diff --git a/public/app/features/transformers/extractFields/extractFields.ts b/public/app/features/transformers/extractFields/extractFields.ts index 2d2707ee959..81628b595ed 100644 --- a/public/app/features/transformers/extractFields/extractFields.ts +++ b/public/app/features/transformers/extractFields/extractFields.ts @@ -1,3 +1,6 @@ +import { isString } from 'lodash'; +import { map } from 'rxjs/operators'; + import { ArrayVector, DataFrame, @@ -8,8 +11,7 @@ import { SynchronousDataTransformerInfo, } from '@grafana/data'; import { findField } from 'app/features/dimensions'; -import { isString } from 'lodash'; -import { map } from 'rxjs/operators'; + import { FieldExtractorID, fieldExtractors } from './fieldExtractors'; export interface ExtractFieldsOptions { diff --git a/public/app/features/transformers/extractFields/fieldExtractor.test.ts b/public/app/features/transformers/extractFields/fieldExtractor.test.ts index 10974eaaa43..fd1b62a7d8d 100644 --- a/public/app/features/transformers/extractFields/fieldExtractor.test.ts +++ b/public/app/features/transformers/extractFields/fieldExtractor.test.ts @@ -14,6 +14,66 @@ describe('Extract fields from text', () => { `); }); + it('Test key-values with single/double quotes', async () => { + const extractor = fieldExtractors.get(FieldExtractorID.KeyValues); + const out = extractor.parse('a="1", "b"=\'2\',c=3 x:y ;\r\nz="d and 4"'); + expect(out).toMatchInlineSnapshot(` + Object { + "a": "1", + "b": "2", + "c": "3", + "x": "y", + "z": "d and 4", + } + `); + }); + + it('Test key-values with nested single/double quotes', async () => { + const extractor = fieldExtractors.get(FieldExtractorID.KeyValues); + const out = extractor.parse( + `a="1", "b"=\'2\',c=3 x:y ;\r\nz="dbl_quotes=\\"Double Quotes\\" sgl_quotes='Single Quotes'"` + ); + + expect(out).toMatchInlineSnapshot(` + Object { + "a": "1", + "b": "2", + "c": "3", + "x": "y", + "z": "dbl_quotes=\\"Double Quotes\\" sgl_quotes='Single Quotes'", + } + `); + }); + + it('Test key-values with nested separator characters', async () => { + const extractor = fieldExtractors.get(FieldExtractorID.KeyValues); + const out = extractor.parse(`a="1", "b"=\'2\',c=3 x:y ;\r\nz="This is; testing& validating, 1=:2"`); + + expect(out).toMatchInlineSnapshot(` + Object { + "a": "1", + "b": "2", + "c": "3", + "x": "y", + "z": "This is; testing& validating, 1=:2", + } + `); + }); + + it('Test key-values where some values are null', async () => { + const extractor = fieldExtractors.get(FieldExtractorID.KeyValues); + const out = extractor.parse(`a=, "b"=\'2\',c=3 x: `); + + expect(out).toMatchInlineSnapshot(` + Object { + "a": "", + "b": "2", + "c": "3", + "x": "", + } + `); + }); + it('Split key+values', async () => { const extractor = fieldExtractors.get(FieldExtractorID.KeyValues); const out = extractor.parse('a="1", "b"=\'2\',c=3 x:y ;\r\nz="7"'); diff --git a/public/app/features/transformers/extractFields/fieldExtractors.ts b/public/app/features/transformers/extractFields/fieldExtractors.ts index 51f2ee6a9b0..45b91aecf4f 100644 --- a/public/app/features/transformers/extractFields/fieldExtractors.ts +++ b/public/app/features/transformers/extractFields/fieldExtractors.ts @@ -19,33 +19,99 @@ const extJSON: FieldExtractor = { }, }; -// strips quotes and leading/trailing braces in prom labels -const stripDecor = /['"]|^\{|\}$/g; -// splits on whitespace and other label pair delimiters -const splitLines = /[\s,;&]+/g; -// splits kv pairs -const splitPair = /[=:]/g; +function parseKeyValuePairs(raw: string): Record { + const buff: string[] = []; // array of characters + let esc = ''; + let key = ''; + const obj: Record = {}; + for (let i = 0; i < raw.length; i++) { + let c = raw[i]; + if (c === esc) { + esc = ''; + c = raw[++i]; + } + + const isEscaped = c === '\\'; + if (isEscaped) { + c = raw[++i]; + } + + // When escaped just append + if (isEscaped || esc.length) { + buff.push(c); + continue; + } + + if (c === `"` || c === `'`) { + esc = c; + } + + switch (c) { + case ':': + case '=': + if (buff.length) { + if (key) { + obj[key] = ''; + } + key = buff.join(''); + buff.length = 0; // clear values + } + break; + + // escape chars + case `"`: + case `'`: + // whitespace + case ` `: + case `\n`: + case `\t`: + case `\r`: + case `\n`: + if (buff.length && key === '') { + obj[buff.join('')] = ''; + buff.length = 0; + } + // seperators + case ',': + case ';': + case '&': + case '{': + case '}': + if (buff.length) { + const val = buff.join(''); + if (key.length) { + obj[key] = val; + key = ''; + } else { + key = val; + } + buff.length = 0; // clear values + } + break; + + // append our buffer + default: + buff.push(c); + if (i === raw.length - 1) { + if (key === '' && buff.length) { + obj[buff.join('')] = ''; + buff.length = 0; + } + } + } + } + + if (key.length) { + obj[key] = buff.join(''); + } + return obj; +} const extLabels: FieldExtractor = { id: FieldExtractorID.KeyValues, name: 'Key+value pairs', description: 'Look for a=b, c: d values in the line', - parse: (v: string) => { - const obj: Record = {}; - - v.trim() - .replace(stripDecor, '') - .split(splitLines) - .forEach((pair) => { - let [k, v] = pair.split(splitPair); - - if (k != null) { - obj[k] = v; - } - }); - - return obj; - }, + parse: parseKeyValuePairs, }; const fmts = [extJSON, extLabels]; diff --git a/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.test.tsx b/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.test.tsx index cd709e4a2c4..c2ac51effb9 100644 --- a/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.test.tsx +++ b/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import { toDataFrame, FieldType } from '@grafana/data'; import { fireEvent, render, screen, getByText, getByLabelText } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { toDataFrame, FieldType } from '@grafana/data'; import { selectOptionInTest } from '@grafana/ui'; + import { Props, FieldToConfigMappingEditor } from './FieldToConfigMappingEditor'; beforeEach(() => { diff --git a/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx b/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx index d65317ac1f9..67df4834c06 100644 --- a/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx +++ b/public/app/features/transformers/fieldToConfigMapping/FieldToConfigMappingEditor.tsx @@ -1,7 +1,10 @@ +import { css } from '@emotion/css'; +import { capitalize } from 'lodash'; import React from 'react'; + import { DataFrame, getFieldDisplayName, GrafanaTheme2, ReducerID, SelectableValue } from '@grafana/data'; import { Select, StatsPicker, useStyles2 } from '@grafana/ui'; -import { css } from '@emotion/css'; + import { configMapHandlers, evaluteFieldMappings, @@ -9,7 +12,6 @@ import { FieldToConfigMapping, lookUpConfigHandler as findConfigHandlerFor, } from '../fieldToConfigMapping/fieldToConfigMapping'; -import { capitalize } from 'lodash'; export interface Props { frame: DataFrame; diff --git a/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts b/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts index 8795d73c5e2..36995d4085d 100644 --- a/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts +++ b/public/app/features/transformers/fieldToConfigMapping/fieldToConfigMapping.ts @@ -1,3 +1,5 @@ +import { isArray } from 'lodash'; + import { anyToNumber, DataFrame, @@ -12,7 +14,6 @@ import { Field, FieldType, } from '@grafana/data'; -import { isArray } from 'lodash'; export interface FieldToConfigMapping { fieldName: string; diff --git a/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx b/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx index 2739bcc1af9..fcc9af08c37 100644 --- a/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx +++ b/public/app/features/transformers/lookupGazetteer/FieldLookupTransformerEditor.tsx @@ -1,4 +1,5 @@ import React, { useCallback } from 'react'; + import { DataTransformerID, FieldNamePickerConfigSettings, @@ -8,12 +9,12 @@ import { TransformerUIProps, FieldType, } from '@grafana/data'; - import { InlineField, InlineFieldRow } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker'; -import { FieldLookupOptions, fieldLookupTransformer } from './fieldLookup'; import { GazetteerPathEditor, GazetteerPathEditorConfigSettings } from 'app/features/geo/editor/GazetteerPathEditor'; +import { FieldLookupOptions, fieldLookupTransformer } from './fieldLookup'; + const fieldNamePickerSettings: StandardEditorsRegistryItem = { settings: { width: 30, diff --git a/public/app/features/transformers/lookupGazetteer/fieldLookup.test.ts b/public/app/features/transformers/lookupGazetteer/fieldLookup.test.ts index 85a68668253..be0f273246f 100644 --- a/public/app/features/transformers/lookupGazetteer/fieldLookup.test.ts +++ b/public/app/features/transformers/lookupGazetteer/fieldLookup.test.ts @@ -2,6 +2,7 @@ import { FieldMatcherID, fieldMatchers, FieldType } from '@grafana/data'; import { toDataFrame } from '@grafana/data/src/dataframe/processDataFrame'; import { DataTransformerID } from '@grafana/data/src/transformations/transformers/ids'; import { frameAsGazetter } from 'app/features/geo/gazetteer/gazetteer'; + import { addFieldsFromGazetteer } from './fieldLookup'; describe('Lookup gazetteer', () => { diff --git a/public/app/features/transformers/lookupGazetteer/fieldLookup.ts b/public/app/features/transformers/lookupGazetteer/fieldLookup.ts index 8bf78259cd4..ccaf1da4926 100644 --- a/public/app/features/transformers/lookupGazetteer/fieldLookup.ts +++ b/public/app/features/transformers/lookupGazetteer/fieldLookup.ts @@ -1,3 +1,5 @@ +import { mergeMap, from } from 'rxjs'; + import { ArrayVector, DataFrame, @@ -9,7 +11,6 @@ import { DataTransformerInfo, } from '@grafana/data'; import { COUNTRIES_GAZETTEER_PATH, Gazetteer, getGazetteer } from 'app/features/geo/gazetteer/gazetteer'; -import { mergeMap, from } from 'rxjs'; export interface FieldLookupOptions { lookupField?: string; diff --git a/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx b/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx index fc31ff4351a..dadb684e2cd 100644 --- a/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx +++ b/public/app/features/transformers/prepareTimeSeries/PrepareTimeSeriesEditor.tsx @@ -1,8 +1,10 @@ -import React, { useCallback } from 'react'; -import { GrafanaTheme2, SelectableValue, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; -import { prepareTimeSeriesTransformer, PrepareTimeSeriesOptions, timeSeriesFormat } from './prepareTimeSeries'; -import { InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; +import React, { useCallback } from 'react'; + +import { GrafanaTheme2, SelectableValue, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; +import { InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui'; + +import { prepareTimeSeriesTransformer, PrepareTimeSeriesOptions, timeSeriesFormat } from './prepareTimeSeries'; const wideInfo = { label: 'Wide time series', diff --git a/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.test.ts b/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.test.ts index 059a0aed15c..4dd2dc7b030 100644 --- a/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.test.ts +++ b/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.test.ts @@ -8,6 +8,7 @@ import { DataFrameType, getFrameDisplayName, } from '@grafana/data'; + import { prepareTimeSeriesTransformer, PrepareTimeSeriesOptions, timeSeriesFormat } from './prepareTimeSeries'; describe('Prepare time series transformer', () => { diff --git a/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.ts b/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.ts index 4d237750620..14b68b3fd3c 100644 --- a/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.ts +++ b/public/app/features/transformers/prepareTimeSeries/prepareTimeSeries.ts @@ -1,3 +1,5 @@ +import { map } from 'rxjs/operators'; + import { SynchronousDataTransformerInfo, DataFrame, @@ -12,7 +14,6 @@ import { ArrayVector, } from '@grafana/data'; import { Labels } from 'app/types/unified-alerting-dto'; -import { map } from 'rxjs/operators'; /** * There is currently an effort to figure out consistent names @@ -301,7 +302,6 @@ export const prepareTimeSeriesTransformer: SynchronousDataTransformerInfo { diff --git a/public/app/features/transformers/rowsToFields/RowsToFieldsTransformerEditor.tsx b/public/app/features/transformers/rowsToFields/RowsToFieldsTransformerEditor.tsx index 727df3b1691..2bac83c6add 100644 --- a/public/app/features/transformers/rowsToFields/RowsToFieldsTransformerEditor.tsx +++ b/public/app/features/transformers/rowsToFields/RowsToFieldsTransformerEditor.tsx @@ -1,8 +1,11 @@ import React from 'react'; + import { PluginState, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; -import { rowsToFieldsTransformer, RowToFieldsTransformOptions } from './rowsToFields'; + import { FieldToConfigMappingEditor } from '../fieldToConfigMapping/FieldToConfigMappingEditor'; +import { rowsToFieldsTransformer, RowToFieldsTransformOptions } from './rowsToFields'; + export interface Props extends TransformerUIProps {} export function RowsToFieldsTransformerEditor({ input, options, onChange }: Props) { diff --git a/public/app/features/transformers/rowsToFields/rowsToFields.test.ts b/public/app/features/transformers/rowsToFields/rowsToFields.test.ts index 29bdff2e750..9b6c6f3b583 100644 --- a/public/app/features/transformers/rowsToFields/rowsToFields.test.ts +++ b/public/app/features/transformers/rowsToFields/rowsToFields.test.ts @@ -1,4 +1,5 @@ import { toDataFrame, FieldType } from '@grafana/data'; + import { rowsToFields } from './rowsToFields'; describe('Rows to fields', () => { diff --git a/public/app/features/transformers/rowsToFields/rowsToFields.ts b/public/app/features/transformers/rowsToFields/rowsToFields.ts index b72b9516752..6874073a6af 100644 --- a/public/app/features/transformers/rowsToFields/rowsToFields.ts +++ b/public/app/features/transformers/rowsToFields/rowsToFields.ts @@ -1,4 +1,5 @@ import { map } from 'rxjs/operators'; + import { ArrayVector, DataFrame, @@ -8,6 +9,7 @@ import { getFieldDisplayName, Labels, } from '@grafana/data'; + import { getFieldConfigFromFrame, FieldToConfigMapping, diff --git a/public/app/features/transformers/spatial/SpatialTransformerEditor.tsx b/public/app/features/transformers/spatial/SpatialTransformerEditor.tsx index a3e1fbe8baf..f61bfb2ffd5 100644 --- a/public/app/features/transformers/spatial/SpatialTransformerEditor.tsx +++ b/public/app/features/transformers/spatial/SpatialTransformerEditor.tsx @@ -1,5 +1,6 @@ -import React, { useEffect } from 'react'; import { css } from '@emotion/css'; +import React, { useEffect } from 'react'; + import { DataTransformerID, FrameGeometrySource, @@ -11,12 +12,12 @@ import { TransformerRegistryItem, TransformerUIProps, } from '@grafana/data'; - -import { isLineBuilderOption, spatialTransformer } from './spatialTransformer'; -import { addLocationFields } from 'app/features/geo/editor/locationEditor'; -import { getDefaultOptions, getTransformerOptionPane } from './optionsHelper'; -import { SpatialCalculation, SpatialOperation, SpatialAction, SpatialTransformOptions } from './models.gen'; import { useTheme2 } from '@grafana/ui'; +import { addLocationFields } from 'app/features/geo/editor/locationEditor'; + +import { SpatialCalculation, SpatialOperation, SpatialAction, SpatialTransformOptions } from './models.gen'; +import { getDefaultOptions, getTransformerOptionPane } from './optionsHelper'; +import { isLineBuilderOption, spatialTransformer } from './spatialTransformer'; // Nothing defined in state const supplier = ( diff --git a/public/app/features/transformers/spatial/optionsHelper.tsx b/public/app/features/transformers/spatial/optionsHelper.tsx index 07ade5b3824..8c60d6527bf 100644 --- a/public/app/features/transformers/spatial/optionsHelper.tsx +++ b/public/app/features/transformers/spatial/optionsHelper.tsx @@ -1,10 +1,11 @@ +import { set, get as lodashGet } from 'lodash'; + import { StandardEditorContext, TransformerUIProps, PanelOptionsEditorBuilder } from '@grafana/data'; -import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; import { NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; -import { set, get as lodashGet } from 'lodash'; -import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; +import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { fillOptionsPaneItems } from 'app/features/dashboard/components/PanelEditor/getVisualizationOptions'; +import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; export function getTransformerOptionPane( props: TransformerUIProps, diff --git a/public/app/features/transformers/spatial/spatialTransformer.ts b/public/app/features/transformers/spatial/spatialTransformer.ts index 03b2a4d2dc3..2c73a5603c6 100644 --- a/public/app/features/transformers/spatial/spatialTransformer.ts +++ b/public/app/features/transformers/spatial/spatialTransformer.ts @@ -1,7 +1,9 @@ +import { mergeMap, from } from 'rxjs'; + import { ArrayVector, DataFrame, DataTransformerID, DataTransformerInfo, FieldType } from '@grafana/data'; import { createGeometryCollection, createLineBetween } from 'app/features/geo/format/utils'; import { getGeometryField, getLocationMatchers } from 'app/features/geo/utils/location'; -import { mergeMap, from } from 'rxjs'; + import { SpatialOperation, SpatialAction, SpatialTransformOptions } from './models.gen'; import { doGeomeryCalculation, toLineString } from './utils'; diff --git a/public/app/features/transformers/spatial/utils.ts b/public/app/features/transformers/spatial/utils.ts index 5a4ab6de23b..c1d1f9dd0dd 100644 --- a/public/app/features/transformers/spatial/utils.ts +++ b/public/app/features/transformers/spatial/utils.ts @@ -1,8 +1,10 @@ -import { ArrayVector, Field, FieldType } from '@grafana/data'; import { getCenter } from 'ol/extent'; import { Geometry, LineString, Point } from 'ol/geom'; import { toLonLat } from 'ol/proj'; import { getArea, getLength } from 'ol/sphere'; + +import { ArrayVector, Field, FieldType } from '@grafana/data'; + import { SpatialCalculation, SpatialCalculationOption } from './models.gen'; /** Will return a field with a single row */ diff --git a/public/app/features/transformers/standardTransformers.ts b/public/app/features/transformers/standardTransformers.ts index 3618d03c246..83ed7104dca 100644 --- a/public/app/features/transformers/standardTransformers.ts +++ b/public/app/features/transformers/standardTransformers.ts @@ -1,28 +1,29 @@ import { TransformerRegistryItem } from '@grafana/data'; -import { reduceTransformRegistryItem } from './editors/ReduceTransformerEditor'; + +import { filterByValueTransformRegistryItem } from './FilterByValueTransformer/FilterByValueTransformerEditor'; +import { heatmapTransformRegistryItem } from './calculateHeatmap/HeatmapTransformerEditor'; +import { configFromQueryTransformRegistryItem } from './configFromQuery/ConfigFromQueryTransformerEditor'; +import { calculateFieldTransformRegistryItem } from './editors/CalculateFieldTransformerEditor'; +import { concatenateTransformRegistryItem } from './editors/ConcatenateTransformerEditor'; +import { convertFieldTypeTransformRegistryItem } from './editors/ConvertFieldTypeTransformerEditor'; import { filterFieldsByNameTransformRegistryItem } from './editors/FilterByNameTransformerEditor'; import { filterFramesByRefIdTransformRegistryItem } from './editors/FilterByRefIdTransformerEditor'; -import { filterByValueTransformRegistryItem } from './FilterByValueTransformer/FilterByValueTransformerEditor'; -import { organizeFieldsTransformRegistryItem } from './editors/OrganizeFieldsTransformerEditor'; -import { seriesToFieldsTransformerRegistryItem } from './editors/SeriesToFieldsTransformerEditor'; -import { calculateFieldTransformRegistryItem } from './editors/CalculateFieldTransformerEditor'; -import { labelsToFieldsTransformerRegistryItem } from './editors/LabelsToFieldsTransformerEditor'; import { groupByTransformRegistryItem } from './editors/GroupByTransformerEditor'; -import { sortByTransformRegistryItem } from './editors/SortByTransformerEditor'; -import { mergeTransformerRegistryItem } from './editors/MergeTransformerEditor'; -import { seriesToRowsTransformerRegistryItem } from './editors/SeriesToRowsTransformerEditor'; -import { concatenateTransformRegistryItem } from './editors/ConcatenateTransformerEditor'; -import { renameByRegexTransformRegistryItem } from './editors/RenameByRegexTransformer'; -import { histogramTransformRegistryItem } from './editors/HistogramTransformerEditor'; -import { rowsToFieldsTransformRegistryItem } from './rowsToFields/RowsToFieldsTransformerEditor'; -import { configFromQueryTransformRegistryItem } from './configFromQuery/ConfigFromQueryTransformerEditor'; -import { prepareTimeseriesTransformerRegistryItem } from './prepareTimeSeries/PrepareTimeSeriesEditor'; -import { convertFieldTypeTransformRegistryItem } from './editors/ConvertFieldTypeTransformerEditor'; -import { fieldLookupTransformRegistryItem } from './lookupGazetteer/FieldLookupTransformerEditor'; -import { extractFieldsTransformRegistryItem } from './extractFields/ExtractFieldsTransformerEditor'; -import { heatmapTransformRegistryItem } from './calculateHeatmap/HeatmapTransformerEditor'; -import { spatialTransformRegistryItem } from './spatial/SpatialTransformerEditor'; import { groupingToMatrixTransformRegistryItem } from './editors/GroupingToMatrixTransformerEditor'; +import { histogramTransformRegistryItem } from './editors/HistogramTransformerEditor'; +import { labelsToFieldsTransformerRegistryItem } from './editors/LabelsToFieldsTransformerEditor'; +import { mergeTransformerRegistryItem } from './editors/MergeTransformerEditor'; +import { organizeFieldsTransformRegistryItem } from './editors/OrganizeFieldsTransformerEditor'; +import { reduceTransformRegistryItem } from './editors/ReduceTransformerEditor'; +import { renameByRegexTransformRegistryItem } from './editors/RenameByRegexTransformer'; +import { seriesToFieldsTransformerRegistryItem } from './editors/SeriesToFieldsTransformerEditor'; +import { seriesToRowsTransformerRegistryItem } from './editors/SeriesToRowsTransformerEditor'; +import { sortByTransformRegistryItem } from './editors/SortByTransformerEditor'; +import { extractFieldsTransformRegistryItem } from './extractFields/ExtractFieldsTransformerEditor'; +import { fieldLookupTransformRegistryItem } from './lookupGazetteer/FieldLookupTransformerEditor'; +import { prepareTimeseriesTransformerRegistryItem } from './prepareTimeSeries/PrepareTimeSeriesEditor'; +import { rowsToFieldsTransformRegistryItem } from './rowsToFields/RowsToFieldsTransformerEditor'; +import { spatialTransformRegistryItem } from './spatial/SpatialTransformerEditor'; export const getStandardTransformers = (): Array> => { return [ diff --git a/public/app/features/transformers/utils.ts b/public/app/features/transformers/utils.ts index 4bb949169a0..6a942d6c55b 100644 --- a/public/app/features/transformers/utils.ts +++ b/public/app/features/transformers/utils.ts @@ -1,6 +1,7 @@ -import { DataFrame, getFieldDisplayName } from '@grafana/data'; import { useMemo } from 'react'; +import { DataFrame, getFieldDisplayName } from '@grafana/data'; + export function useAllFieldNamesFromDataFrames(input: DataFrame[]): string[] { return useMemo(() => { if (!Array.isArray(input)) { diff --git a/public/app/features/users/TokenRevokedModal.tsx b/public/app/features/users/TokenRevokedModal.tsx index 9ba3ed36df1..912a1819ff2 100644 --- a/public/app/features/users/TokenRevokedModal.tsx +++ b/public/app/features/users/TokenRevokedModal.tsx @@ -1,8 +1,9 @@ +import { css, cx } from '@emotion/css'; import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, InfoBox, Portal, stylesFactory, useTheme2 } from '@grafana/ui'; import { getModalStyles } from '@grafana/ui/src/components/Modal/getModalStyles'; -import { css, cx } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; interface Props { maxConcurrentSessions?: number; diff --git a/public/app/features/users/UsersActionBar.test.tsx b/public/app/features/users/UsersActionBar.test.tsx index ea04bb7cbdb..a9e9c4b4490 100644 --- a/public/app/features/users/UsersActionBar.test.tsx +++ b/public/app/features/users/UsersActionBar.test.tsx @@ -1,7 +1,8 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { Props, UsersActionBar } from './UsersActionBar'; +import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + +import { Props, UsersActionBar } from './UsersActionBar'; import { setUsersSearchQuery } from './state/reducers'; jest.mock('app/core/core', () => ({ diff --git a/public/app/features/users/UsersActionBar.tsx b/public/app/features/users/UsersActionBar.tsx index 9e4765490fd..05b1166b2c1 100644 --- a/public/app/features/users/UsersActionBar.tsx +++ b/public/app/features/users/UsersActionBar.tsx @@ -1,12 +1,15 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; -import { setUsersSearchQuery } from './state/reducers'; -import { selectTotal } from '../invites/state/selectors'; -import { getUsersSearchQuery } from './state/selectors'; + import { RadioButtonGroup, LinkButton, FilterInput } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; +import { selectTotal } from '../invites/state/selectors'; + +import { setUsersSearchQuery } from './state/reducers'; +import { getUsersSearchQuery } from './state/selectors'; + export interface Props { searchQuery: string; setUsersSearchQuery: typeof setUsersSearchQuery; diff --git a/public/app/features/users/UsersListPage.test.tsx b/public/app/features/users/UsersListPage.test.tsx index 4907b7403a6..bc334b6287a 100644 --- a/public/app/features/users/UsersListPage.test.tsx +++ b/public/app/features/users/UsersListPage.test.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { Props, UsersListPage } from './UsersListPage'; -import { Invitee, OrgUser } from 'app/types'; -// import { getMockUser } from './__mocks__/userMocks'; -import { NavModel } from '@grafana/data'; +import React from 'react'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; + +import { NavModel } from '@grafana/data'; +import { Invitee, OrgUser } from 'app/types'; + +import { Props, UsersListPage } from './UsersListPage'; import { setUsersSearchPage, setUsersSearchQuery } from './state/reducers'; jest.mock('../../core/app_events', () => ({ diff --git a/public/app/features/users/UsersListPage.tsx b/public/app/features/users/UsersListPage.tsx index 1083b4f9ac1..f53b3f47b9f 100644 --- a/public/app/features/users/UsersListPage.tsx +++ b/public/app/features/users/UsersListPage.tsx @@ -1,19 +1,21 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { renderMarkdown } from '@grafana/data'; import { HorizontalGroup, Pagination, VerticalGroup } from '@grafana/ui'; - import Page from 'app/core/components/Page/Page'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { OrgUser, OrgRole, StoreState } from 'app/types'; + +import InviteesTable from '../invites/InviteesTable'; +import { fetchInvitees } from '../invites/state/actions'; +import { selectInvitesMatchingQuery } from '../invites/state/selectors'; + import UsersActionBar from './UsersActionBar'; import UsersTable from './UsersTable'; -import InviteesTable from '../invites/InviteesTable'; -import { OrgUser, OrgRole, StoreState } from 'app/types'; import { loadUsers, removeUser, updateUser } from './state/actions'; -import { fetchInvitees } from '../invites/state/actions'; -import { getNavModel } from 'app/core/selectors/navModel'; -import { getUsers, getUsersSearchQuery, getUsersSearchPage } from './state/selectors'; import { setUsersSearchQuery, setUsersSearchPage } from './state/reducers'; -import { selectInvitesMatchingQuery } from '../invites/state/selectors'; +import { getUsers, getUsersSearchQuery, getUsersSearchPage } from './state/selectors'; function mapStateToProps(state: StoreState) { const searchQuery = getUsersSearchQuery(state.users); diff --git a/public/app/features/users/UsersTable.test.tsx b/public/app/features/users/UsersTable.test.tsx index f71e0517eb7..8782c0bdb64 100644 --- a/public/app/features/users/UsersTable.test.tsx +++ b/public/app/features/users/UsersTable.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import UsersTable, { Props } from './UsersTable'; -import { OrgUser } from 'app/types'; -import { getMockUsers } from './__mocks__/userMocks'; +import React from 'react'; + import { ConfirmModal } from '@grafana/ui'; +import { OrgUser } from 'app/types'; + +import UsersTable, { Props } from './UsersTable'; +import { getMockUsers } from './__mocks__/userMocks'; jest.mock('app/core/core', () => ({ contextSrv: { diff --git a/public/app/features/users/UsersTable.tsx b/public/app/features/users/UsersTable.tsx index c5c05f1faf2..aee80c96897 100644 --- a/public/app/features/users/UsersTable.tsx +++ b/public/app/features/users/UsersTable.tsx @@ -1,11 +1,13 @@ import React, { FC, useEffect, useState } from 'react'; -import { AccessControlAction, OrgUser, Role } from 'app/types'; -import { OrgRolePicker } from '../admin/OrgRolePicker'; -import { Button, ConfirmModal } from '@grafana/ui'; + import { OrgRole } from '@grafana/data'; -import { contextSrv } from 'app/core/core'; -import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api'; +import { Button, ConfirmModal } from '@grafana/ui'; import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker'; +import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction, OrgUser, Role } from 'app/types'; + +import { OrgRolePicker } from '../admin/OrgRolePicker'; export interface Props { users: OrgUser[]; diff --git a/public/app/features/users/state/actions.ts b/public/app/features/users/state/actions.ts index 8d36742c584..259d0d1829d 100644 --- a/public/app/features/users/state/actions.ts +++ b/public/app/features/users/state/actions.ts @@ -1,8 +1,10 @@ -import { ThunkResult } from '../../../types'; import { getBackendSrv } from '@grafana/runtime'; -import { OrgUser } from 'app/types'; -import { usersLoaded } from './reducers'; import { accessControlQueryParam } from 'app/core/utils/accessControl'; +import { OrgUser } from 'app/types'; + +import { ThunkResult } from '../../../types'; + +import { usersLoaded } from './reducers'; export function loadUsers(): ThunkResult { return async (dispatch) => { diff --git a/public/app/features/users/state/reducers.test.ts b/public/app/features/users/state/reducers.test.ts index 6499959c449..c5634126f0b 100644 --- a/public/app/features/users/state/reducers.test.ts +++ b/public/app/features/users/state/reducers.test.ts @@ -1,8 +1,9 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { UsersState } from '../../../types'; -import { initialState, setUsersSearchQuery, usersLoaded, usersReducer } from './reducers'; import { getMockUsers } from '../__mocks__/userMocks'; +import { initialState, setUsersSearchQuery, usersLoaded, usersReducer } from './reducers'; + describe('usersReducer', () => { describe('when usersLoaded is dispatched', () => { it('then state should be correct', () => { diff --git a/public/app/features/users/state/reducers.ts b/public/app/features/users/state/reducers.ts index 436feccdd78..8b77465357b 100644 --- a/public/app/features/users/state/reducers.ts +++ b/public/app/features/users/state/reducers.ts @@ -1,7 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { OrgUser, UsersState } from 'app/types'; import config from 'app/core/config'; +import { OrgUser, UsersState } from 'app/types'; export const initialState: UsersState = { users: [] as OrgUser[], diff --git a/public/app/features/variables/adapters.ts b/public/app/features/variables/adapters.ts index ad76e41229d..0f52ab42f4a 100644 --- a/public/app/features/variables/adapters.ts +++ b/public/app/features/variables/adapters.ts @@ -1,11 +1,12 @@ import { ComponentType } from 'react'; import { Reducer } from 'redux'; + import { Registry, UrlQueryValue, VariableType } from '@grafana/data'; -import { VariableModel, VariableOption } from './types'; import { VariableEditorProps } from './editor/types'; import { VariablePickerProps } from './pickers/types'; import { VariablesState } from './state/types'; +import { VariableModel, VariableOption } from './types'; export interface VariableAdapter { id: VariableType; diff --git a/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx b/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx index 2bb3a2d7289..d27f1546daa 100644 --- a/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx +++ b/public/app/features/variables/adhoc/AdHocVariableEditor.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { selectOptionInTest } from '@grafana/ui'; import { getSelectParent } from '@grafana/ui/src/components/Select/test-utils'; -import { AdHocVariableEditorUnConnected as AdHocVariableEditor } from './AdHocVariableEditor'; import { adHocBuilder } from '../shared/testing/builders'; +import { AdHocVariableEditorUnConnected as AdHocVariableEditor } from './AdHocVariableEditor'; + const props = { extended: { dataSources: [ diff --git a/public/app/features/variables/adhoc/AdHocVariableEditor.tsx b/public/app/features/variables/adhoc/AdHocVariableEditor.tsx index 3e4ca258479..0aa81cd7d9b 100644 --- a/public/app/features/variables/adhoc/AdHocVariableEditor.tsx +++ b/public/app/features/variables/adhoc/AdHocVariableEditor.tsx @@ -1,19 +1,21 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { Alert, InlineFieldRow, VerticalGroup } from '@grafana/ui'; -import { DataSourceRef, SelectableValue } from '@grafana/data'; -import { AdHocVariableModel } from '../types'; -import { VariableEditorProps } from '../editor/types'; -import { initialVariableEditorState } from '../editor/reducer'; -import { changeVariableDatasource, initAdHocVariableEditor } from './actions'; +import { DataSourceRef, SelectableValue } from '@grafana/data'; +import { Alert, InlineFieldRow, VerticalGroup } from '@grafana/ui'; import { StoreState } from 'app/types'; + import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableSelectField } from '../editor/VariableSelectField'; +import { initialVariableEditorState } from '../editor/reducer'; import { getAdhocVariableEditorState } from '../editor/selectors'; +import { VariableEditorProps } from '../editor/types'; import { getVariablesState } from '../state/selectors'; +import { AdHocVariableModel } from '../types'; import { toKeyedVariableIdentifier } from '../utils'; +import { changeVariableDatasource, initAdHocVariableEditor } from './actions'; + const mapStateToProps = (state: StoreState, ownProps: OwnProps) => { const { rootStateKey } = ownProps.variable; diff --git a/public/app/features/variables/adhoc/actions.test.ts b/public/app/features/variables/adhoc/actions.test.ts index becf0bb4a76..22c3d49b1e0 100644 --- a/public/app/features/variables/adhoc/actions.test.ts +++ b/public/app/features/variables/adhoc/actions.test.ts @@ -1,9 +1,16 @@ import { DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { VariableModel } from 'app/features/variables/types'; -import { variableAdapters } from '../adapters'; -import { createAdHocVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { variableAdapters } from '../adapters'; +import { changeVariableEditorExtended, setIdInEditor } from '../editor/reducer'; +import { adHocBuilder } from '../shared/testing/builders'; import { getPreloadedState, getRootReducer, RootReducerType } from '../state/helpers'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { addVariable, changeVariableProp } from '../state/sharedReducer'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + import { addFilter, AdHocTableOptions, @@ -14,14 +21,8 @@ import { removeFilter, setFiltersFromUrl, } from './actions'; +import { createAdHocVariableAdapter } from './adapter'; import { filterAdded, filterRemoved, filtersRestored, filterUpdated } from './reducer'; -import { addVariable, changeVariableProp } from '../state/sharedReducer'; -import { VariableModel } from 'app/features/variables/types'; -import { changeVariableEditorExtended, setIdInEditor } from '../editor/reducer'; -import { adHocBuilder } from '../shared/testing/builders'; -import { locationService } from '@grafana/runtime'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; const getList = jest.fn().mockReturnValue([]); const getDatasource = jest.fn().mockResolvedValue({}); diff --git a/public/app/features/variables/adhoc/actions.ts b/public/app/features/variables/adhoc/actions.ts index b49e398e524..596c60cdc49 100644 --- a/public/app/features/variables/adhoc/actions.ts +++ b/public/app/features/variables/adhoc/actions.ts @@ -1,10 +1,20 @@ import { cloneDeep } from 'lodash'; -import { StoreState, ThunkResult } from 'app/types'; + +import { DataSourceRef, getDataSourceRef } from '@grafana/data'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; +import { StoreState, ThunkResult } from 'app/types'; + import { changeVariableEditorExtended } from '../editor/reducer'; -import { addVariable, changeVariableProp } from '../state/sharedReducer'; +import { getAdhocVariableEditorState } from '../editor/selectors'; +import { isAdHoc } from '../guard'; +import { variableUpdated } from '../state/actions'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; import { getLastKey, getNewVariableIndex, getVariable, getVariablesState } from '../state/selectors'; +import { addVariable, changeVariableProp } from '../state/sharedReducer'; import { AddVariable, KeyedVariableIdentifier } from '../state/types'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + import { AdHocVariabelFilterUpdate, filterAdded, @@ -13,13 +23,6 @@ import { filterUpdated, initialAdHocVariableModelState, } from './reducer'; -import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; -import { variableUpdated } from '../state/actions'; -import { isAdHoc } from '../guard'; -import { DataSourceRef, getDataSourceRef } from '@grafana/data'; -import { getAdhocVariableEditorState } from '../editor/selectors'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; export interface AdHocTableOptions { datasource: DataSourceRef; diff --git a/public/app/features/variables/adhoc/adapter.ts b/public/app/features/variables/adhoc/adapter.ts index 6f2ed40e074..f5e9587de50 100644 --- a/public/app/features/variables/adhoc/adapter.ts +++ b/public/app/features/variables/adhoc/adapter.ts @@ -1,14 +1,15 @@ import { cloneDeep } from 'lodash'; -import { AdHocVariableModel } from '../types'; import { dispatch } from '../../../store/store'; import { VariableAdapter } from '../adapters'; -import { AdHocPicker } from './picker/AdHocPicker'; -import { adHocVariableReducer, initialAdHocVariableModelState } from './reducer'; +import { AdHocVariableModel } from '../types'; +import { toKeyedVariableIdentifier } from '../utils'; + import { AdHocVariableEditor } from './AdHocVariableEditor'; import { setFiltersFromUrl } from './actions'; +import { AdHocPicker } from './picker/AdHocPicker'; +import { adHocVariableReducer, initialAdHocVariableModelState } from './reducer'; import * as urlParser from './urlParser'; -import { toKeyedVariableIdentifier } from '../utils'; const noop = async () => {}; diff --git a/public/app/features/variables/adhoc/picker/AdHocFilter.test.tsx b/public/app/features/variables/adhoc/picker/AdHocFilter.test.tsx index cccdc3f8a1c..932f1731c24 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilter.test.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilter.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import selectEvent from 'react-select-event'; -import { AdHocFilter } from './AdHocFilter'; -import { AdHocVariableFilter } from '../../types'; + import { setDataSourceSrv } from '../../../../../../packages/grafana-runtime'; +import { AdHocVariableFilter } from '../../types'; + +import { AdHocFilter } from './AdHocFilter'; describe('AdHocFilter', () => { it('renders filters', async () => { @@ -20,13 +22,13 @@ describe('AdHocFilter', () => { const { addFilter } = setup(); // Select key - userEvent.click(screen.getByLabelText('Add Filter')); + await userEvent.click(screen.getByLabelText('Add Filter')); const selectEl = screen.getByTestId('AdHocFilterKey-add-key-wrapper'); expect(selectEl).toBeInTheDocument(); await selectEvent.select(selectEl, 'key3', { container: document.body }); // Select value - userEvent.click(screen.getByText('select value')); + await userEvent.click(screen.getByText('select value')); // There are already some filters rendered const selectEl2 = screen.getAllByTestId('AdHocFilterValue-value-wrapper')[2]; await selectEvent.select(selectEl2, 'val3', { container: document.body }); @@ -39,7 +41,7 @@ describe('AdHocFilter', () => { const { removeFilter } = setup(); // Select key - userEvent.click(screen.getByText('key1')); + await userEvent.click(screen.getByText('key1')); const selectEl = screen.getAllByTestId('AdHocFilterKey-key-wrapper')[0]; expect(selectEl).toBeInTheDocument(); await selectEvent.select(selectEl, '-- remove filter --', { container: document.body }); @@ -52,7 +54,7 @@ describe('AdHocFilter', () => { const { changeFilter } = setup(); // Select key - userEvent.click(screen.getByText('val1')); + await userEvent.click(screen.getByText('val1')); const selectEl = screen.getAllByTestId('AdHocFilterValue-value-wrapper')[0]; expect(selectEl).toBeInTheDocument(); await selectEvent.select(selectEl, 'val4', { container: document.body }); diff --git a/public/app/features/variables/adhoc/picker/AdHocFilter.tsx b/public/app/features/variables/adhoc/picker/AdHocFilter.tsx index ca072d118ea..a7300a4177f 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilter.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilter.tsx @@ -1,10 +1,12 @@ import React, { PureComponent, ReactNode } from 'react'; -import { AdHocVariableFilter } from 'app/features/variables/types'; + import { DataSourceRef, SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter } from 'app/features/variables/types'; + import { AdHocFilterBuilder } from './AdHocFilterBuilder'; -import { ConditionSegment } from './ConditionSegment'; import { REMOVE_FILTER_KEY } from './AdHocFilterKey'; import { AdHocFilterRenderer } from './AdHocFilterRenderer'; +import { ConditionSegment } from './ConditionSegment'; interface Props { datasource: DataSourceRef | null; diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx index 06424e95cfa..d8b3e822bd5 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterBuilder.tsx @@ -1,6 +1,8 @@ import React, { FC, useCallback, useState } from 'react'; -import { AdHocVariableFilter } from 'app/features/variables/types'; + import { DataSourceRef, SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter } from 'app/features/variables/types'; + import { AdHocFilterKey, REMOVE_FILTER_KEY } from './AdHocFilterKey'; import { AdHocFilterRenderer } from './AdHocFilterRenderer'; diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx index a99afca4cde..48b2e0ee6c0 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx @@ -1,7 +1,9 @@ import React, { FC, ReactElement } from 'react'; -import { Icon, SegmentAsync } from '@grafana/ui'; -import { getDatasourceSrv } from '../../../plugins/datasource_srv'; + import { DataSourceRef, SelectableValue } from '@grafana/data'; +import { Icon, SegmentAsync } from '@grafana/ui'; + +import { getDatasourceSrv } from '../../../plugins/datasource_srv'; interface Props { datasource: DataSourceRef; diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx index a3664125f57..9ed209f9031 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx @@ -1,9 +1,11 @@ import React, { FC } from 'react'; -import { OperatorSegment } from './OperatorSegment'; -import { AdHocVariableFilter } from 'app/features/variables/types'; + import { DataSourceRef, SelectableValue } from '@grafana/data'; +import { AdHocVariableFilter } from 'app/features/variables/types'; + import { AdHocFilterKey } from './AdHocFilterKey'; import { AdHocFilterValue } from './AdHocFilterValue'; +import { OperatorSegment } from './OperatorSegment'; interface Props { datasource: DataSourceRef; diff --git a/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx b/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx index 0eaf2d54620..1d2fd45bb81 100644 --- a/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx @@ -1,7 +1,9 @@ import React, { FC } from 'react'; -import { SegmentAsync } from '@grafana/ui'; -import { getDatasourceSrv } from '../../../plugins/datasource_srv'; + import { DataSourceRef, MetricFindValue, SelectableValue } from '@grafana/data'; +import { SegmentAsync } from '@grafana/ui'; + +import { getDatasourceSrv } from '../../../plugins/datasource_srv'; interface Props { datasource: DataSourceRef; diff --git a/public/app/features/variables/adhoc/picker/AdHocPicker.tsx b/public/app/features/variables/adhoc/picker/AdHocPicker.tsx index 1fa6e55e1ce..9343f4aa0a8 100644 --- a/public/app/features/variables/adhoc/picker/AdHocPicker.tsx +++ b/public/app/features/variables/adhoc/picker/AdHocPicker.tsx @@ -1,10 +1,13 @@ import React, { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; + import { VariablePickerProps } from '../../pickers/types'; -import { addFilter, changeFilter, removeFilter } from '../actions'; -import { AdHocFilter } from './AdHocFilter'; import { toKeyedVariableIdentifier } from '../../utils'; +import { addFilter, changeFilter, removeFilter } from '../actions'; + +import { AdHocFilter } from './AdHocFilter'; const mapDispatchToProps = { addFilter, diff --git a/public/app/features/variables/adhoc/picker/OperatorSegment.tsx b/public/app/features/variables/adhoc/picker/OperatorSegment.tsx index f42702b8204..dcd4eb9575d 100644 --- a/public/app/features/variables/adhoc/picker/OperatorSegment.tsx +++ b/public/app/features/variables/adhoc/picker/OperatorSegment.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; -import { Segment } from '@grafana/ui'; + import { SelectableValue } from '@grafana/data'; +import { Segment } from '@grafana/ui'; interface Props { value: string; diff --git a/public/app/features/variables/adhoc/reducer.test.ts b/public/app/features/variables/adhoc/reducer.test.ts index ce4b7948cd4..0f34801e2c1 100644 --- a/public/app/features/variables/adhoc/reducer.test.ts +++ b/public/app/features/variables/adhoc/reducer.test.ts @@ -1,12 +1,14 @@ -import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { cloneDeep } from 'lodash'; + +import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { getVariableTestContext } from '../state/helpers'; import { VariablesState } from '../state/types'; -import { adHocVariableReducer, filterAdded, filterRemoved, filtersRestored, filterUpdated } from './reducer'; import { AdHocVariableFilter, AdHocVariableModel } from '../types'; -import { createAdHocVariableAdapter } from './adapter'; import { toVariablePayload } from '../utils'; +import { createAdHocVariableAdapter } from './adapter'; +import { adHocVariableReducer, filterAdded, filterRemoved, filtersRestored, filterUpdated } from './reducer'; + describe('adHocVariableReducer', () => { const adapter = createAdHocVariableAdapter(); diff --git a/public/app/features/variables/adhoc/reducer.ts b/public/app/features/variables/adhoc/reducer.ts index f5ff0945961..267257ad2b0 100644 --- a/public/app/features/variables/adhoc/reducer.ts +++ b/public/app/features/variables/adhoc/reducer.ts @@ -1,8 +1,9 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { AdHocVariableFilter, AdHocVariableModel, initialVariableModelState } from 'app/features/variables/types'; -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; + import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; export interface AdHocVariabelFilterUpdate { index: number; diff --git a/public/app/features/variables/adhoc/urlParser.test.ts b/public/app/features/variables/adhoc/urlParser.test.ts index a163809b0c7..bb4d6326a34 100644 --- a/public/app/features/variables/adhoc/urlParser.test.ts +++ b/public/app/features/variables/adhoc/urlParser.test.ts @@ -1,6 +1,7 @@ -import { toFilters, toUrl } from './urlParser'; -import { AdHocVariableFilter } from 'app/features/variables/types'; import { UrlQueryValue } from '@grafana/data'; +import { AdHocVariableFilter } from 'app/features/variables/types'; + +import { toFilters, toUrl } from './urlParser'; describe('urlParser', () => { describe('parsing toUrl with no filters', () => { diff --git a/public/app/features/variables/adhoc/urlParser.ts b/public/app/features/variables/adhoc/urlParser.ts index 8003fd159ab..4d6db307fa6 100644 --- a/public/app/features/variables/adhoc/urlParser.ts +++ b/public/app/features/variables/adhoc/urlParser.ts @@ -1,7 +1,8 @@ -import { AdHocVariableFilter } from 'app/features/variables/types'; -import { UrlQueryValue } from '@grafana/data'; import { isArray, isString } from 'lodash'; +import { UrlQueryValue } from '@grafana/data'; +import { AdHocVariableFilter } from 'app/features/variables/types'; + export const toUrl = (filters: AdHocVariableFilter[]): string[] => { return filters.map((filter) => toArray(filter).map(escapeDelimiter).join('|')); }; diff --git a/public/app/features/variables/constant/ConstantVariableEditor.tsx b/public/app/features/variables/constant/ConstantVariableEditor.tsx index a054aebf240..d5cb25d0e5f 100644 --- a/public/app/features/variables/constant/ConstantVariableEditor.tsx +++ b/public/app/features/variables/constant/ConstantVariableEditor.tsx @@ -1,11 +1,12 @@ import React, { FormEvent, PureComponent } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; import { VerticalGroup } from '@grafana/ui'; -import { ConstantVariableModel } from '../types'; -import { VariableEditorProps } from '../editor/types'; import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableTextField } from '../editor/VariableTextField'; +import { VariableEditorProps } from '../editor/types'; +import { ConstantVariableModel } from '../types'; export interface Props extends VariableEditorProps {} diff --git a/public/app/features/variables/constant/actions.test.ts b/public/app/features/variables/constant/actions.test.ts index a609cdc69ce..a593715b404 100644 --- a/public/app/features/variables/constant/actions.test.ts +++ b/public/app/features/variables/constant/actions.test.ts @@ -1,14 +1,15 @@ -import { variableAdapters } from '../adapters'; -import { createConstantVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { updateConstantVariableOptions } from './actions'; +import { variableAdapters } from '../adapters'; import { getRootReducer, RootReducerType } from '../state/helpers'; -import { ConstantVariableModel, initialVariableModelState, VariableOption } from '../types'; -import { createConstantOptionsFromQuery } from './reducer'; -import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; +import { ConstantVariableModel, initialVariableModelState, VariableOption } from '../types'; import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; +import { updateConstantVariableOptions } from './actions'; +import { createConstantVariableAdapter } from './adapter'; +import { createConstantOptionsFromQuery } from './reducer'; + describe('constant actions', () => { variableAdapters.setInit(() => [createConstantVariableAdapter()]); diff --git a/public/app/features/variables/constant/actions.ts b/public/app/features/variables/constant/actions.ts index 6f865bcaa19..87f78229983 100644 --- a/public/app/features/variables/constant/actions.ts +++ b/public/app/features/variables/constant/actions.ts @@ -1,10 +1,12 @@ -import { validateVariableSelectionState } from '../state/actions'; import { ThunkResult } from 'app/types'; -import { createConstantOptionsFromQuery } from './reducer'; -import { KeyedVariableIdentifier } from '../state/types'; + +import { validateVariableSelectionState } from '../state/actions'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { KeyedVariableIdentifier } from '../state/types'; import { toVariablePayload } from '../utils'; +import { createConstantOptionsFromQuery } from './reducer'; + export const updateConstantVariableOptions = (identifier: KeyedVariableIdentifier): ThunkResult => { return async (dispatch) => { const { rootStateKey } = identifier; diff --git a/public/app/features/variables/constant/adapter.ts b/public/app/features/variables/constant/adapter.ts index 4555a63c788..cd304d46ced 100644 --- a/public/app/features/variables/constant/adapter.ts +++ b/public/app/features/variables/constant/adapter.ts @@ -1,13 +1,15 @@ import { cloneDeep } from 'lodash'; -import { ConstantVariableModel } from '../types'; + import { dispatch } from '../../../store/store'; -import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; -import { constantVariableReducer, initialConstantVariableModelState } from './reducer'; +import { optionPickerFactory } from '../pickers'; +import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; +import { ConstantVariableModel } from '../types'; +import { toKeyedVariableIdentifier } from '../utils'; + import { ConstantVariableEditor } from './ConstantVariableEditor'; import { updateConstantVariableOptions } from './actions'; -import { optionPickerFactory } from '../pickers'; -import { toKeyedVariableIdentifier } from '../utils'; +import { constantVariableReducer, initialConstantVariableModelState } from './reducer'; export const createConstantVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/constant/reducer.test.ts b/public/app/features/variables/constant/reducer.test.ts index 932593582b2..4b64b64e87b 100644 --- a/public/app/features/variables/constant/reducer.test.ts +++ b/public/app/features/variables/constant/reducer.test.ts @@ -3,11 +3,12 @@ import { cloneDeep } from 'lodash'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { getVariableTestContext } from '../state/helpers'; import { VariablesState } from '../state/types'; -import { constantVariableReducer, createConstantOptionsFromQuery } from './reducer'; import { ConstantVariableModel } from '../types'; -import { createConstantVariableAdapter } from './adapter'; import { toVariablePayload } from '../utils'; +import { createConstantVariableAdapter } from './adapter'; +import { constantVariableReducer, createConstantOptionsFromQuery } from './reducer'; + describe('constantVariableReducer', () => { const adapter = createConstantVariableAdapter(); diff --git a/public/app/features/variables/constant/reducer.ts b/public/app/features/variables/constant/reducer.ts index 8a830d779d1..c6b4eac74f8 100644 --- a/public/app/features/variables/constant/reducer.ts +++ b/public/app/features/variables/constant/reducer.ts @@ -1,8 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ConstantVariableModel, initialVariableModelState, VariableHide, VariableOption } from '../types'; -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; +import { ConstantVariableModel, initialVariableModelState, VariableHide, VariableOption } from '../types'; export const initialConstantVariableModelState: ConstantVariableModel = { ...initialVariableModelState, diff --git a/public/app/features/variables/custom/CustomVariableEditor.tsx b/public/app/features/variables/custom/CustomVariableEditor.tsx index 64a76e1b3b6..bdc179bf10a 100644 --- a/public/app/features/variables/custom/CustomVariableEditor.tsx +++ b/public/app/features/variables/custom/CustomVariableEditor.tsx @@ -1,15 +1,17 @@ import React, { FormEvent, PureComponent } from 'react'; -import { CustomVariableModel, VariableWithMultiSupport } from '../types'; -import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; -import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; -import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { VerticalGroup } from '@grafana/ui'; + import { selectors } from '@grafana/e2e-selectors'; +import { VerticalGroup } from '@grafana/ui'; +import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; import { StoreState } from 'app/types'; -import { changeVariableMultiValue } from '../state/actions'; + +import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableTextAreaField } from '../editor/VariableTextAreaField'; +import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; +import { changeVariableMultiValue } from '../state/actions'; +import { CustomVariableModel, VariableWithMultiSupport } from '../types'; interface OwnProps extends VariableEditorProps {} diff --git a/public/app/features/variables/custom/actions.test.ts b/public/app/features/variables/custom/actions.test.ts index 9df0916264d..d537fb1b414 100644 --- a/public/app/features/variables/custom/actions.test.ts +++ b/public/app/features/variables/custom/actions.test.ts @@ -1,13 +1,14 @@ +import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { variableAdapters } from '../adapters'; +import { getRootReducer, RootReducerType } from '../state/helpers'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; +import { CustomVariableModel, initialVariableModelState, VariableOption } from '../types'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + import { updateCustomVariableOptions } from './actions'; import { createCustomVariableAdapter } from './adapter'; -import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { getRootReducer, RootReducerType } from '../state/helpers'; -import { CustomVariableModel, initialVariableModelState, VariableOption } from '../types'; -import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; import { createCustomOptionsFromQuery } from './reducer'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; describe('custom actions', () => { variableAdapters.setInit(() => [createCustomVariableAdapter()]); diff --git a/public/app/features/variables/custom/actions.ts b/public/app/features/variables/custom/actions.ts index 557d2044dca..898412fe49b 100644 --- a/public/app/features/variables/custom/actions.ts +++ b/public/app/features/variables/custom/actions.ts @@ -1,10 +1,12 @@ -import { validateVariableSelectionState } from '../state/actions'; import { ThunkResult } from 'app/types'; -import { createCustomOptionsFromQuery } from './reducer'; -import { KeyedVariableIdentifier } from '../state/types'; + +import { validateVariableSelectionState } from '../state/actions'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { KeyedVariableIdentifier } from '../state/types'; import { toVariablePayload } from '../utils'; +import { createCustomOptionsFromQuery } from './reducer'; + export const updateCustomVariableOptions = (identifier: KeyedVariableIdentifier): ThunkResult => { return async (dispatch) => { const { rootStateKey } = identifier; diff --git a/public/app/features/variables/custom/adapter.ts b/public/app/features/variables/custom/adapter.ts index 47af02927bc..c98374f195d 100644 --- a/public/app/features/variables/custom/adapter.ts +++ b/public/app/features/variables/custom/adapter.ts @@ -1,14 +1,16 @@ import { cloneDeep } from 'lodash'; -import { CustomVariableModel } from '../types'; + import { dispatch } from '../../../store/store'; -import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; -import { customVariableReducer, initialCustomVariableModelState } from './reducer'; +import { ALL_VARIABLE_TEXT } from '../constants'; +import { optionPickerFactory } from '../pickers'; +import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; +import { CustomVariableModel } from '../types'; +import { isAllVariable, toKeyedVariableIdentifier } from '../utils'; + import { CustomVariableEditor } from './CustomVariableEditor'; import { updateCustomVariableOptions } from './actions'; -import { isAllVariable, toKeyedVariableIdentifier } from '../utils'; -import { optionPickerFactory } from '../pickers'; -import { ALL_VARIABLE_TEXT } from '../constants'; +import { customVariableReducer, initialCustomVariableModelState } from './reducer'; export const createCustomVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/custom/reducer.test.ts b/public/app/features/variables/custom/reducer.test.ts index 39f0ef09815..23ad676a298 100644 --- a/public/app/features/variables/custom/reducer.test.ts +++ b/public/app/features/variables/custom/reducer.test.ts @@ -1,14 +1,15 @@ import { cloneDeep } from 'lodash'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; import { getVariableTestContext } from '../state/helpers'; import { VariablesState } from '../state/types'; -import { createCustomOptionsFromQuery, customVariableReducer } from './reducer'; -import { createCustomVariableAdapter } from './adapter'; import { CustomVariableModel } from '../types'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; import { toVariablePayload } from '../utils'; +import { createCustomVariableAdapter } from './adapter'; +import { createCustomOptionsFromQuery, customVariableReducer } from './reducer'; + describe('customVariableReducer', () => { const adapter = createCustomVariableAdapter(); diff --git a/public/app/features/variables/custom/reducer.ts b/public/app/features/variables/custom/reducer.ts index 9a897030c30..4c5c744d657 100644 --- a/public/app/features/variables/custom/reducer.ts +++ b/public/app/features/variables/custom/reducer.ts @@ -1,9 +1,9 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { CustomVariableModel, initialVariableModelState, VariableOption } from '../types'; -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; +import { CustomVariableModel, initialVariableModelState, VariableOption } from '../types'; export const initialCustomVariableModelState: CustomVariableModel = { ...initialVariableModelState, diff --git a/public/app/features/variables/datasource/DataSourceVariableEditor.test.tsx b/public/app/features/variables/datasource/DataSourceVariableEditor.test.tsx index 42190e73bb4..68a58da3835 100644 --- a/public/app/features/variables/datasource/DataSourceVariableEditor.test.tsx +++ b/public/app/features/variables/datasource/DataSourceVariableEditor.test.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; +import React from 'react'; + +import { selectOptionInTest } from '@grafana/ui'; +import { getSelectParent } from '@grafana/ui/src/components/Select/test-utils'; import { DataSourceVariableEditorUnConnected as DataSourceVariableEditor } from './DataSourceVariableEditor'; import { initialDataSourceVariableModelState } from './reducer'; -import { selectOptionInTest } from '@grafana/ui'; -import { getSelectParent } from '@grafana/ui/src/components/Select/test-utils'; const props = { extended: { diff --git a/public/app/features/variables/datasource/DataSourceVariableEditor.tsx b/public/app/features/variables/datasource/DataSourceVariableEditor.tsx index 51f9a827f71..eacd5f98139 100644 --- a/public/app/features/variables/datasource/DataSourceVariableEditor.tsx +++ b/public/app/features/variables/datasource/DataSourceVariableEditor.tsx @@ -1,21 +1,23 @@ import React, { FormEvent, PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; + import { SelectableValue } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { InlineFieldRow, VerticalGroup } from '@grafana/ui'; -import { DataSourceVariableModel, VariableWithMultiSupport } from '../types'; -import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; -import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; -import { initialVariableEditorState } from '../editor/reducer'; -import { initDataSourceVariableEditor } from './actions'; import { StoreState } from '../../../types'; -import { changeVariableMultiValue } from '../state/actions'; +import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableSelectField } from '../editor/VariableSelectField'; import { VariableTextField } from '../editor/VariableTextField'; -import { getVariablesState } from '../state/selectors'; -import { selectors } from '@grafana/e2e-selectors'; +import { initialVariableEditorState } from '../editor/reducer'; import { getDatasourceVariableEditorState } from '../editor/selectors'; +import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; +import { changeVariableMultiValue } from '../state/actions'; +import { getVariablesState } from '../state/selectors'; +import { DataSourceVariableModel, VariableWithMultiSupport } from '../types'; + +import { initDataSourceVariableEditor } from './actions'; const mapStateToProps = (state: StoreState, ownProps: OwnProps) => { const { diff --git a/public/app/features/variables/datasource/actions.test.ts b/public/app/features/variables/datasource/actions.test.ts index 67f62788eae..3d4f669fce6 100644 --- a/public/app/features/variables/datasource/actions.test.ts +++ b/public/app/features/variables/datasource/actions.test.ts @@ -1,22 +1,23 @@ import { DataSourceInstanceSettings } from '@grafana/data'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { getRootReducer, RootReducerType } from '../state/helpers'; +import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; import { variableAdapters } from '../adapters'; -import { createDataSourceVariableAdapter } from './adapter'; +import { changeVariableEditorExtended } from '../editor/reducer'; +import { datasourceBuilder } from '../shared/testing/builders'; +import { getDataSourceInstanceSetting } from '../shared/testing/helpers'; +import { getRootReducer, RootReducerType } from '../state/helpers'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + import { DataSourceVariableActionDependencies, initDataSourceVariableEditor, updateDataSourceVariableOptions, } from './actions'; -import { getMockPlugin } from '../../plugins/__mocks__/pluginMocks'; +import { createDataSourceVariableAdapter } from './adapter'; import { createDataSourceOptions } from './reducer'; -import { addVariable, setCurrentVariableValue } from '../state/sharedReducer'; -import { changeVariableEditorExtended } from '../editor/reducer'; -import { datasourceBuilder } from '../shared/testing/builders'; -import { getDataSourceInstanceSetting } from '../shared/testing/helpers'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; interface Args { sources?: DataSourceInstanceSettings[]; diff --git a/public/app/features/variables/datasource/actions.ts b/public/app/features/variables/datasource/actions.ts index dc7b34cdd75..33708cd493a 100644 --- a/public/app/features/variables/datasource/actions.ts +++ b/public/app/features/variables/datasource/actions.ts @@ -1,18 +1,20 @@ import { chain } from 'lodash'; -import { getTemplateSrv } from '@grafana/runtime'; -import { stringToJsRegex } from '@grafana/data'; -import { KeyedVariableIdentifier } from '../state/types'; +import { stringToJsRegex } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; + import { ThunkResult } from '../../../types'; -import { createDataSourceOptions } from './reducer'; -import { validateVariableSelectionState } from '../state/actions'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; -import { getVariable } from '../state/selectors'; -import { DataSourceVariableModel } from '../types'; import { changeVariableEditorExtended } from '../editor/reducer'; +import { validateVariableSelectionState } from '../state/actions'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { getVariable } from '../state/selectors'; +import { KeyedVariableIdentifier } from '../state/types'; +import { DataSourceVariableModel } from '../types'; import { toVariablePayload } from '../utils'; +import { createDataSourceOptions } from './reducer'; + export interface DataSourceVariableActionDependencies { getDatasourceSrv: typeof getDatasourceSrv; } diff --git a/public/app/features/variables/datasource/adapter.ts b/public/app/features/variables/datasource/adapter.ts index 9b4da36805a..d43b82f403c 100644 --- a/public/app/features/variables/datasource/adapter.ts +++ b/public/app/features/variables/datasource/adapter.ts @@ -1,14 +1,16 @@ import { cloneDeep } from 'lodash'; -import { DataSourceVariableModel } from '../types'; + import { dispatch } from '../../../store/store'; -import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; -import { dataSourceVariableReducer, initialDataSourceVariableModelState } from './reducer'; +import { ALL_VARIABLE_TEXT } from '../constants'; +import { optionPickerFactory } from '../pickers'; +import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; +import { DataSourceVariableModel } from '../types'; +import { containsVariable, isAllVariable, toKeyedVariableIdentifier } from '../utils'; + import { DataSourceVariableEditor } from './DataSourceVariableEditor'; import { updateDataSourceVariableOptions } from './actions'; -import { containsVariable, isAllVariable, toKeyedVariableIdentifier } from '../utils'; -import { optionPickerFactory } from '../pickers'; -import { ALL_VARIABLE_TEXT } from '../constants'; +import { dataSourceVariableReducer, initialDataSourceVariableModelState } from './reducer'; export const createDataSourceVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/datasource/reducer.test.ts b/public/app/features/variables/datasource/reducer.test.ts index dce1a1ed93f..31a096eb3cc 100644 --- a/public/app/features/variables/datasource/reducer.test.ts +++ b/public/app/features/variables/datasource/reducer.test.ts @@ -1,16 +1,18 @@ import { cloneDeep } from 'lodash'; + import { DataSourceInstanceSettings } from '@grafana/data'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { createDataSourceOptions, dataSourceVariableReducer } from './reducer'; -import { DataSourceVariableModel } from '../types'; -import { getVariableTestContext } from '../state/helpers'; -import { createDataSourceVariableAdapter } from './adapter'; -import { VariablesState } from '../state/types'; import { getMockPlugins } from '../../plugins/__mocks__/pluginMocks'; import { getDataSourceInstanceSetting } from '../shared/testing/helpers'; +import { getVariableTestContext } from '../state/helpers'; +import { VariablesState } from '../state/types'; +import { DataSourceVariableModel } from '../types'; import { toVariablePayload } from '../utils'; +import { createDataSourceVariableAdapter } from './adapter'; +import { createDataSourceOptions, dataSourceVariableReducer } from './reducer'; + describe('dataSourceVariableReducer', () => { const adapter = createDataSourceVariableAdapter(); describe('when createDataSourceOptions is dispatched', () => { diff --git a/public/app/features/variables/datasource/reducer.ts b/public/app/features/variables/datasource/reducer.ts index 3e2370dd495..eda07cac254 100644 --- a/public/app/features/variables/datasource/reducer.ts +++ b/public/app/features/variables/datasource/reducer.ts @@ -1,10 +1,11 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + import { DataSourceInstanceSettings } from '@grafana/data'; -import { DataSourceVariableModel, initialVariableModelState, VariableOption, VariableRefresh } from '../types'; -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; +import { DataSourceVariableModel, initialVariableModelState, VariableOption, VariableRefresh } from '../types'; export const initialDataSourceVariableModelState: DataSourceVariableModel = { ...initialVariableModelState, diff --git a/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx b/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx index 19ef28e01d3..cee7cae4083 100644 --- a/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx +++ b/public/app/features/variables/editor/LegacyVariableQueryEditor.tsx @@ -1,11 +1,13 @@ +import { css } from '@emotion/css'; import React, { FC, useCallback, useState } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; -import { css } from '@emotion/css'; + +import { VariableQueryEditorProps } from '../types'; import { VariableTextAreaField } from './VariableTextAreaField'; -import { VariableQueryEditorProps } from '../types'; export const LEGACY_VARIABLE_QUERY_EDITOR_NAME = 'Grafana-LegacyVariableQueryEditor'; diff --git a/public/app/features/variables/editor/SelectionOptionsEditor.tsx b/public/app/features/variables/editor/SelectionOptionsEditor.tsx index d1e2688cc40..00288f9d8d4 100644 --- a/public/app/features/variables/editor/SelectionOptionsEditor.tsx +++ b/public/app/features/variables/editor/SelectionOptionsEditor.tsx @@ -1,14 +1,16 @@ import React, { ChangeEvent, FormEvent, FunctionComponent, useCallback } from 'react'; -import { InlineFieldRow, VerticalGroup } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; -import { VariableWithMultiSupport } from '../types'; -import { VariableEditorProps } from './types'; +import { selectors } from '@grafana/e2e-selectors'; +import { InlineFieldRow, VerticalGroup } from '@grafana/ui'; + import { KeyedVariableIdentifier } from '../state/types'; +import { VariableWithMultiSupport } from '../types'; +import { toKeyedVariableIdentifier } from '../utils'; + import { VariableSectionHeader } from './VariableSectionHeader'; import { VariableSwitchField } from './VariableSwitchField'; import { VariableTextField } from './VariableTextField'; -import { toKeyedVariableIdentifier } from '../utils'; +import { VariableEditorProps } from './types'; export interface SelectionOptionsEditorProps extends VariableEditorProps { diff --git a/public/app/features/variables/editor/VariableEditorContainer.tsx b/public/app/features/variables/editor/VariableEditorContainer.tsx index 3170eb9f35d..9245d4d93ce 100644 --- a/public/app/features/variables/editor/VariableEditorContainer.tsx +++ b/public/app/features/variables/editor/VariableEditorContainer.tsx @@ -1,10 +1,11 @@ -import { selectors } from '@grafana/e2e-selectors'; -import { Button, Icon } from '@grafana/ui'; -import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import React, { MouseEvent, PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { selectors } from '@grafana/e2e-selectors'; +import { Button, Icon } from '@grafana/ui'; +import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; + import { StoreState, ThunkDispatch } from '../../../types'; import { VariablesDependenciesButton } from '../inspect/VariablesDependenciesButton'; import { VariablesUnknownTable } from '../inspect/VariablesUnknownTable'; @@ -13,9 +14,10 @@ import { getEditorVariables, getVariablesState } from '../state/selectors'; import { changeVariableOrder, duplicateVariable, removeVariable } from '../state/sharedReducer'; import { KeyedVariableIdentifier } from '../state/types'; import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; -import { switchToEditMode, switchToListMode, switchToNewMode } from './actions'; + import { VariableEditorEditor } from './VariableEditorEditor'; import { VariableEditorList } from './VariableEditorList'; +import { switchToEditMode, switchToListMode, switchToNewMode } from './actions'; const mapStateToProps = (state: StoreState, ownProps: OwnProps) => { const { uid } = ownProps.dashboard; diff --git a/public/app/features/variables/editor/VariableEditorEditor.tsx b/public/app/features/variables/editor/VariableEditorEditor.tsx index 58441e2eff1..43f5d70483f 100644 --- a/public/app/features/variables/editor/VariableEditorEditor.tsx +++ b/public/app/features/variables/editor/VariableEditorEditor.tsx @@ -1,29 +1,31 @@ -import React, { FormEvent, PureComponent } from 'react'; -import { bindActionCreators } from 'redux'; -import { connect, ConnectedProps } from 'react-redux'; import { isEqual } from 'lodash'; -import { AppEvents, LoadingState, SelectableValue, VariableType } from '@grafana/data'; -import { Button, Icon, InlineFieldRow, VerticalGroup } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; +import React, { FormEvent, PureComponent } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { AppEvents, LoadingState, SelectableValue, VariableType } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Button, Icon, InlineFieldRow, VerticalGroup } from '@grafana/ui'; + +import { appEvents } from '../../../core/core'; +import { StoreState, ThunkDispatch } from '../../../types'; import { variableAdapters } from '../adapters'; +import { hasOptions } from '../guard'; +import { updateOptions } from '../state/actions'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { getVariable, getVariablesState } from '../state/selectors'; +import { changeVariableProp, changeVariableType } from '../state/sharedReducer'; import { KeyedVariableIdentifier } from '../state/types'; import { VariableHide } from '../types'; -import { appEvents } from '../../../core/core'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + +import { VariableHideSelect } from './VariableHideSelect'; +import { VariableSectionHeader } from './VariableSectionHeader'; +import { VariableTextField } from './VariableTextField'; +import { VariableTypeSelect } from './VariableTypeSelect'; import { VariableValuesPreview } from './VariableValuesPreview'; import { changeVariableName, onEditorUpdate, variableEditorMount, variableEditorUnMount } from './actions'; import { OnPropChangeArguments } from './types'; -import { changeVariableProp, changeVariableType } from '../state/sharedReducer'; -import { updateOptions } from '../state/actions'; -import { VariableTextField } from './VariableTextField'; -import { VariableSectionHeader } from './VariableSectionHeader'; -import { hasOptions } from '../guard'; -import { VariableTypeSelect } from './VariableTypeSelect'; -import { VariableHideSelect } from './VariableHideSelect'; -import { getVariable, getVariablesState } from '../state/selectors'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { StoreState, ThunkDispatch } from '../../../types'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; const mapStateToProps = (state: StoreState, ownProps: OwnProps) => ({ editor: getVariablesState(ownProps.identifier.rootStateKey, state).editor, diff --git a/public/app/features/variables/editor/VariableEditorList.tsx b/public/app/features/variables/editor/VariableEditorList.tsx index c3ac0e585cd..e6c052e358a 100644 --- a/public/app/features/variables/editor/VariableEditorList.tsx +++ b/public/app/features/variables/editor/VariableEditorList.tsx @@ -1,14 +1,16 @@ import React, { ReactElement } from 'react'; import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; + import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; - -import { VariableModel } from '../types'; -import { KeyedVariableIdentifier } from '../state/types'; -import { UsagesToNetwork, VariableUsageTree } from '../inspect/utils'; -import { VariableEditorListRow } from './VariableEditorListRow'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { UsagesToNetwork, VariableUsageTree } from '../inspect/utils'; +import { KeyedVariableIdentifier } from '../state/types'; +import { VariableModel } from '../types'; + +import { VariableEditorListRow } from './VariableEditorListRow'; + export interface Props { variables: VariableModel[]; usages: VariableUsageTree[]; diff --git a/public/app/features/variables/editor/VariableEditorListRow.tsx b/public/app/features/variables/editor/VariableEditorListRow.tsx index 272f29314ea..4873a926286 100644 --- a/public/app/features/variables/editor/VariableEditorListRow.tsx +++ b/public/app/features/variables/editor/VariableEditorListRow.tsx @@ -1,14 +1,15 @@ import { css } from '@emotion/css'; +import React, { ReactElement } from 'react'; +import { Draggable } from 'react-beautiful-dnd'; + import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; import { Button, Icon, IconButton, useStyles2, useTheme2 } from '@grafana/ui'; -import React, { ReactElement } from 'react'; -import { Draggable } from 'react-beautiful-dnd'; import { hasOptions, isAdHoc, isQuery } from '../guard'; -import { getVariableUsages, UsagesToNetwork, VariableUsageTree } from '../inspect/utils'; import { VariableUsagesButton } from '../inspect/VariableUsagesButton'; +import { getVariableUsages, UsagesToNetwork, VariableUsageTree } from '../inspect/utils'; import { KeyedVariableIdentifier } from '../state/types'; import { VariableModel } from '../types'; import { toKeyedVariableIdentifier } from '../utils'; diff --git a/public/app/features/variables/editor/VariableHideSelect.tsx b/public/app/features/variables/editor/VariableHideSelect.tsx index bccf9e740f7..8018ff23a42 100644 --- a/public/app/features/variables/editor/VariableHideSelect.tsx +++ b/public/app/features/variables/editor/VariableHideSelect.tsx @@ -1,4 +1,5 @@ import React, { PropsWithChildren, useMemo } from 'react'; + import { SelectableValue, VariableType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; diff --git a/public/app/features/variables/editor/VariableSectionHeader.tsx b/public/app/features/variables/editor/VariableSectionHeader.tsx index 72ccf3109ff..407d2c6eb75 100644 --- a/public/app/features/variables/editor/VariableSectionHeader.tsx +++ b/public/app/features/variables/editor/VariableSectionHeader.tsx @@ -1,7 +1,8 @@ -import React, { PropsWithChildren, ReactElement } from 'react'; -import { useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { PropsWithChildren, ReactElement } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { useStyles } from '@grafana/ui'; interface VariableSectionHeaderProps { name: string; diff --git a/public/app/features/variables/editor/VariableSelectField.tsx b/public/app/features/variables/editor/VariableSelectField.tsx index 4ef00603c35..b08aca6ab5c 100644 --- a/public/app/features/variables/editor/VariableSelectField.tsx +++ b/public/app/features/variables/editor/VariableSelectField.tsx @@ -1,7 +1,8 @@ -import React, { PropsWithChildren, ReactElement } from 'react'; -import { InlineFormLabel, Select, useStyles } from '@grafana/ui'; -import { GrafanaTheme, SelectableValue } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { PropsWithChildren, ReactElement } from 'react'; + +import { GrafanaTheme, SelectableValue } from '@grafana/data'; +import { InlineFormLabel, Select, useStyles } from '@grafana/ui'; import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; interface VariableSelectFieldProps { diff --git a/public/app/features/variables/editor/VariableSwitchField.tsx b/public/app/features/variables/editor/VariableSwitchField.tsx index da796823354..aa3cb42de03 100644 --- a/public/app/features/variables/editor/VariableSwitchField.tsx +++ b/public/app/features/variables/editor/VariableSwitchField.tsx @@ -1,4 +1,5 @@ import React, { ChangeEvent, PropsWithChildren, ReactElement } from 'react'; + import { InlineField, InlineSwitch } from '@grafana/ui'; import { useUniqueId } from 'app/plugins/datasource/influxdb/components/useUniqueId'; interface VariableSwitchFieldProps { diff --git a/public/app/features/variables/editor/VariableTextAreaField.tsx b/public/app/features/variables/editor/VariableTextAreaField.tsx index 5298b0c53a2..60263be1386 100644 --- a/public/app/features/variables/editor/VariableTextAreaField.tsx +++ b/public/app/features/variables/editor/VariableTextAreaField.tsx @@ -1,7 +1,8 @@ -import React, { FormEvent, PropsWithChildren, ReactElement, useCallback } from 'react'; -import { InlineField, TextArea, useStyles } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FormEvent, PropsWithChildren, ReactElement, useCallback } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { InlineField, TextArea, useStyles } from '@grafana/ui'; interface VariableTextAreaFieldProps { name: string; diff --git a/public/app/features/variables/editor/VariableTextField.tsx b/public/app/features/variables/editor/VariableTextField.tsx index 1e77844199a..5d8b5c4ed61 100644 --- a/public/app/features/variables/editor/VariableTextField.tsx +++ b/public/app/features/variables/editor/VariableTextField.tsx @@ -1,4 +1,5 @@ import React, { FormEvent, PropsWithChildren, ReactElement } from 'react'; + import { InlineField, Input, PopoverContent } from '@grafana/ui'; interface VariableTextFieldProps { diff --git a/public/app/features/variables/editor/VariableTypeSelect.tsx b/public/app/features/variables/editor/VariableTypeSelect.tsx index 907f4ebc9c7..7f5a0b26cbd 100644 --- a/public/app/features/variables/editor/VariableTypeSelect.tsx +++ b/public/app/features/variables/editor/VariableTypeSelect.tsx @@ -1,10 +1,11 @@ import React, { PropsWithChildren, useMemo } from 'react'; + import { SelectableValue, VariableType } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { variableAdapters } from '../adapters'; import { VariableSelectField } from '../editor/VariableSelectField'; import { getVariableTypes } from '../utils'; -import { variableAdapters } from '../adapters'; interface Props { onChange: (option: SelectableValue) => void; diff --git a/public/app/features/variables/editor/VariableValuesPreview.tsx b/public/app/features/variables/editor/VariableValuesPreview.tsx index 248e470f040..9623c72c50b 100644 --- a/public/app/features/variables/editor/VariableValuesPreview.tsx +++ b/public/app/features/variables/editor/VariableValuesPreview.tsx @@ -1,9 +1,11 @@ +import { css } from '@emotion/css'; import React, { MouseEvent, useCallback, useEffect, useState } from 'react'; -import { VariableOption, VariableWithOptions } from '../types'; + +import { GrafanaTheme } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Button, InlineFieldRow, InlineLabel, useStyles, VerticalGroup } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; -import { css } from '@emotion/css'; + +import { VariableOption, VariableWithOptions } from '../types'; export interface VariableValuesPreviewProps { variable: VariableWithOptions; diff --git a/public/app/features/variables/editor/actions.test.ts b/public/app/features/variables/editor/actions.test.ts index 755259d977f..57ee80e95a7 100644 --- a/public/app/features/variables/editor/actions.test.ts +++ b/public/app/features/variables/editor/actions.test.ts @@ -1,14 +1,15 @@ +import { initialState } from '../../dashboard/state/reducers'; import { variableAdapters } from '../adapters'; +import { createConstantVariableAdapter } from '../constant/adapter'; import { initialConstantVariableModelState } from '../constant/reducer'; +import * as inspectUtils from '../inspect/utils'; import { constantBuilder, customBuilder } from '../shared/testing/builders'; import { initialKeyedVariablesState, toKeyedAction } from '../state/keyedVariablesReducer'; import * as selectors from '../state/selectors'; -import * as inspectUtils from '../inspect/utils'; import { addVariable } from '../state/sharedReducer'; -import { createConstantVariableAdapter } from '../constant/adapter'; + import { getNextAvailableId, switchToListMode, switchToNewMode } from './actions'; import { setIdInEditor } from './reducer'; -import { initialState } from '../../dashboard/state/reducers'; describe('getNextAvailableId', () => { describe('when called with a custom type and there is already 2 variables', () => { @@ -56,8 +57,15 @@ describe('switchToListMode', () => { const mockDispatch = jest.fn(); switchToListMode(null)(mockDispatch, mockGetState, undefined); + const keyedAction = { + type: expect.any(String), + payload: { + key: 'null', + action: expect.any(Object), + }, + }; expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch.mock.calls[0][0]).toEqual(toKeyedAction('null', expect.any(Object))); - expect(mockDispatch.mock.calls[1][0]).toEqual(toKeyedAction('null', expect.any(Object))); + expect(mockDispatch.mock.calls[0][0]).toMatchObject(keyedAction); + expect(mockDispatch.mock.calls[1][0]).toMatchObject(keyedAction); }); }); diff --git a/public/app/features/variables/editor/actions.ts b/public/app/features/variables/editor/actions.ts index 00d92fe1ffc..263ccac3b0c 100644 --- a/public/app/features/variables/editor/actions.ts +++ b/public/app/features/variables/editor/actions.ts @@ -1,5 +1,7 @@ -import { VariableType } from '@grafana/data'; import { cloneDeep } from 'lodash'; + +import { VariableType } from '@grafana/data'; + import { ThunkResult } from '../../../types'; import { variableAdapters } from '../adapters'; import { initInspect } from '../inspect/reducer'; @@ -11,6 +13,7 @@ import { addVariable, removeVariable } from '../state/sharedReducer'; import { AddVariable, KeyedVariableIdentifier, VariableIdentifier } from '../state/types'; import { VariableModel } from '../types'; import { toKeyedVariableIdentifier, toStateKey, toVariablePayload } from '../utils'; + import { changeVariableNameFailed, changeVariableNameSucceeded, diff --git a/public/app/features/variables/editor/getVariableQueryEditor.test.tsx b/public/app/features/variables/editor/getVariableQueryEditor.test.tsx index 3eab35ea809..a69e66195f7 100644 --- a/public/app/features/variables/editor/getVariableQueryEditor.test.tsx +++ b/public/app/features/variables/editor/getVariableQueryEditor.test.tsx @@ -1,6 +1,7 @@ import { VariableSupportType } from '@grafana/data'; -import { getVariableQueryEditor, StandardVariableQueryEditor } from './getVariableQueryEditor'; + import { LegacyVariableQueryEditor } from './LegacyVariableQueryEditor'; +import { getVariableQueryEditor, StandardVariableQueryEditor } from './getVariableQueryEditor'; describe('getVariableQueryEditor', () => { describe('happy cases', () => { diff --git a/public/app/features/variables/editor/getVariableQueryEditor.tsx b/public/app/features/variables/editor/getVariableQueryEditor.tsx index 5824530e5d9..d3557d7f07a 100644 --- a/public/app/features/variables/editor/getVariableQueryEditor.tsx +++ b/public/app/features/variables/editor/getVariableQueryEditor.tsx @@ -1,17 +1,19 @@ import React, { useCallback } from 'react'; + import { DataQuery, DataSourceApi, DataSourceJsonData, QueryEditorProps, StandardVariableQuery } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; -import { LegacyVariableQueryEditor } from './LegacyVariableQueryEditor'; +import { importDataSourcePlugin } from '../../plugins/plugin_loader'; import { hasCustomVariableSupport, hasDatasourceVariableSupport, hasLegacyVariableSupport, hasStandardVariableSupport, } from '../guard'; -import { importDataSourcePlugin } from '../../plugins/plugin_loader'; import { VariableQueryEditorType } from '../types'; +import { LegacyVariableQueryEditor } from './LegacyVariableQueryEditor'; + export async function getVariableQueryEditor< TQuery extends DataQuery = DataQuery, TOptions extends DataSourceJsonData = DataSourceJsonData, diff --git a/public/app/features/variables/editor/reducer.test.ts b/public/app/features/variables/editor/reducer.test.ts index e1ed467bb3f..dde10201c0a 100644 --- a/public/app/features/variables/editor/reducer.test.ts +++ b/public/app/features/variables/editor/reducer.test.ts @@ -1,4 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { toVariablePayload } from '../utils'; + import { addVariableEditorError, changeVariableEditorExtended, @@ -14,7 +16,6 @@ import { VariableEditorState, variableEditorUnMounted, } from './reducer'; -import { toVariablePayload } from '../utils'; describe('variableEditorReducer', () => { describe('when setIdInEditor is dispatched', () => { diff --git a/public/app/features/variables/editor/reducer.ts b/public/app/features/variables/editor/reducer.ts index 5e0fcbcce33..530a486bd6c 100644 --- a/public/app/features/variables/editor/reducer.ts +++ b/public/app/features/variables/editor/reducer.ts @@ -1,5 +1,7 @@ -import { DataSourceApi, DataSourceRef } from '@grafana/data'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { DataSourceApi, DataSourceRef } from '@grafana/data'; + import { VariablePayload } from '../state/types'; import { VariableQueryEditorType } from '../types'; diff --git a/public/app/features/variables/editor/selectors.test.ts b/public/app/features/variables/editor/selectors.test.ts index 6380d9153df..7281ec468b3 100644 --- a/public/app/features/variables/editor/selectors.test.ts +++ b/public/app/features/variables/editor/selectors.test.ts @@ -1,16 +1,17 @@ -import { - getAdhocVariableEditorState, - getDatasourceVariableEditorState, - getQueryVariableEditorState, -} from './selectors'; +import { DataSourceApi } from '@grafana/data'; + +import { LegacyVariableQueryEditor } from './LegacyVariableQueryEditor'; import { AdHocVariableEditorState, DataSourceVariableEditorState, initialVariableEditorState, QueryVariableEditorState, } from './reducer'; -import { LegacyVariableQueryEditor } from './LegacyVariableQueryEditor'; -import { DataSourceApi } from '@grafana/data'; +import { + getAdhocVariableEditorState, + getDatasourceVariableEditorState, + getQueryVariableEditorState, +} from './selectors'; const adhocExtended: AdHocVariableEditorState = { dataSources: [ diff --git a/public/app/features/variables/getAllVariableValuesForUrl.test.ts b/public/app/features/variables/getAllVariableValuesForUrl.test.ts index dbd04260212..8d8ef1172c6 100644 --- a/public/app/features/variables/getAllVariableValuesForUrl.test.ts +++ b/public/app/features/variables/getAllVariableValuesForUrl.test.ts @@ -1,9 +1,11 @@ import { setTemplateSrv } from '@grafana/runtime'; -import { variableAdapters } from './adapters'; -import { createQueryVariableAdapter } from './query/adapter'; -import { getVariablesUrlParams } from './getAllVariableValuesForUrl'; + import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv'; +import { variableAdapters } from './adapters'; +import { getVariablesUrlParams } from './getAllVariableValuesForUrl'; +import { createQueryVariableAdapter } from './query/adapter'; + const key = 'key'; describe('getAllVariableValuesForUrl', () => { diff --git a/public/app/features/variables/getAllVariableValuesForUrl.ts b/public/app/features/variables/getAllVariableValuesForUrl.ts index e775394f287..629904673ad 100644 --- a/public/app/features/variables/getAllVariableValuesForUrl.ts +++ b/public/app/features/variables/getAllVariableValuesForUrl.ts @@ -1,5 +1,6 @@ import { ScopedVars, UrlQueryMap } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; + import { variableAdapters } from './adapters'; export function getVariablesUrlParams(scopedVars?: ScopedVars): UrlQueryMap { diff --git a/public/app/features/variables/guard.test.ts b/public/app/features/variables/guard.test.ts index 081ee056be5..476fbf4289b 100644 --- a/public/app/features/variables/guard.test.ts +++ b/public/app/features/variables/guard.test.ts @@ -1,3 +1,7 @@ +import { VariableSupportType } from '@grafana/data'; + +import { LegacyVariableQueryEditor } from './editor/LegacyVariableQueryEditor'; +import { StandardVariableQueryEditor } from './editor/getVariableQueryEditor'; import { hasCustomVariableSupport, hasDatasourceVariableSupport, @@ -6,9 +10,6 @@ import { isLegacyQueryEditor, isQueryEditor, } from './guard'; -import { LegacyVariableQueryEditor } from './editor/LegacyVariableQueryEditor'; -import { StandardVariableQueryEditor } from './editor/getVariableQueryEditor'; -import { VariableSupportType } from '@grafana/data'; describe('type guards', () => { describe('hasLegacyVariableSupport', () => { diff --git a/public/app/features/variables/guard.ts b/public/app/features/variables/guard.ts index f5006df682c..0efb21229b5 100644 --- a/public/app/features/variables/guard.ts +++ b/public/app/features/variables/guard.ts @@ -1,5 +1,6 @@ import { ComponentType } from 'react'; import { Observable } from 'rxjs'; + import { CustomVariableSupport, DataQuery, @@ -15,6 +16,7 @@ import { VariableSupportType, } from '@grafana/data'; +import { LEGACY_VARIABLE_QUERY_EDITOR_NAME } from './editor/LegacyVariableQueryEditor'; import { AdHocVariableModel, ConstantVariableModel, @@ -24,7 +26,6 @@ import { VariableWithMultiSupport, VariableWithOptions, } from './types'; -import { LEGACY_VARIABLE_QUERY_EDITOR_NAME } from './editor/LegacyVariableQueryEditor'; export const isQuery = (model: VariableModel): model is QueryVariableModel => { return model.type === 'query'; diff --git a/public/app/features/variables/inspect/NetworkGraph.tsx b/public/app/features/variables/inspect/NetworkGraph.tsx index c72c00b8c6f..38d7707bfef 100644 --- a/public/app/features/variables/inspect/NetworkGraph.tsx +++ b/public/app/features/variables/inspect/NetworkGraph.tsx @@ -1,4 +1,5 @@ import React, { FC, useCallback, useEffect, useRef } from 'react'; + import { GraphEdge, GraphNode } from './utils'; interface OwnProps { diff --git a/public/app/features/variables/inspect/NetworkGraphModal.tsx b/public/app/features/variables/inspect/NetworkGraphModal.tsx index bcabe37d643..2effdbb2b4e 100644 --- a/public/app/features/variables/inspect/NetworkGraphModal.tsx +++ b/public/app/features/variables/inspect/NetworkGraphModal.tsx @@ -1,8 +1,9 @@ import React, { useCallback, useState } from 'react'; + import { Modal } from '@grafana/ui'; -import { GraphEdge, GraphNode } from './utils'; import { NetworkGraph, Props as NetWorkGraphProps } from './NetworkGraph'; +import { GraphEdge, GraphNode } from './utils'; interface NetworkGraphModalApi { showModal: () => void; diff --git a/public/app/features/variables/inspect/VariableUsagesButton.tsx b/public/app/features/variables/inspect/VariableUsagesButton.tsx index 1cc89513179..58a7d2625ca 100644 --- a/public/app/features/variables/inspect/VariableUsagesButton.tsx +++ b/public/app/features/variables/inspect/VariableUsagesButton.tsx @@ -1,9 +1,10 @@ import React, { FC, useMemo } from 'react'; -import { IconButton } from '@grafana/ui'; -import { reportInteraction } from '@grafana/runtime'; -import { UsagesToNetwork } from './utils'; +import { reportInteraction } from '@grafana/runtime'; +import { IconButton } from '@grafana/ui'; + import { NetworkGraphModal } from './NetworkGraphModal'; +import { UsagesToNetwork } from './utils'; interface Props { id: string; diff --git a/public/app/features/variables/inspect/VariablesDependenciesButton.tsx b/public/app/features/variables/inspect/VariablesDependenciesButton.tsx index 142e6fcaca3..e2a76a6e6f6 100644 --- a/public/app/features/variables/inspect/VariablesDependenciesButton.tsx +++ b/public/app/features/variables/inspect/VariablesDependenciesButton.tsx @@ -1,12 +1,14 @@ import React, { FC, useMemo } from 'react'; import { Provider } from 'react-redux'; -import { Button } from '@grafana/ui'; -import { reportInteraction } from '@grafana/runtime'; -import { createDependencyEdges, createDependencyNodes, filterNodesWithDependencies } from './utils'; +import { reportInteraction } from '@grafana/runtime'; +import { Button } from '@grafana/ui'; + import { store } from '../../../store/store'; import { VariableModel } from '../types'; + import { NetworkGraphModal } from './NetworkGraphModal'; +import { createDependencyEdges, createDependencyNodes, filterNodesWithDependencies } from './utils'; interface OwnProps { variables: VariableModel[]; diff --git a/public/app/features/variables/inspect/VariablesUnknownButton.tsx b/public/app/features/variables/inspect/VariablesUnknownButton.tsx index a8ec1998118..1e37f0986dc 100644 --- a/public/app/features/variables/inspect/VariablesUnknownButton.tsx +++ b/public/app/features/variables/inspect/VariablesUnknownButton.tsx @@ -1,7 +1,9 @@ import React, { FC, useMemo } from 'react'; + import { IconButton } from '@grafana/ui'; -import { UsagesToNetwork } from './utils'; + import { NetworkGraphModal } from './NetworkGraphModal'; +import { UsagesToNetwork } from './utils'; interface Props { id: string; diff --git a/public/app/features/variables/inspect/VariablesUnknownTable.test.tsx b/public/app/features/variables/inspect/VariablesUnknownTable.test.tsx index 0173b108dc0..e6dd04e8ba6 100644 --- a/public/app/features/variables/inspect/VariablesUnknownTable.test.tsx +++ b/public/app/features/variables/inspect/VariablesUnknownTable.test.tsx @@ -1,10 +1,17 @@ -import React from 'react'; -import * as runtime from '@grafana/runtime'; -import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/dist/types/setup'; +import React from 'react'; + +import * as runtime from '@grafana/runtime'; -import { VariablesUnknownTable, VariablesUnknownTableProps } from './VariablesUnknownTable'; import { customBuilder } from '../shared/testing/builders'; + +import { + SLOW_VARIABLES_EXPANSION_THRESHOLD, + VariablesUnknownTable, + VariablesUnknownTableProps, +} from './VariablesUnknownTable'; import * as utils from './utils'; import { UsagesToNetwork } from './utils'; @@ -36,25 +43,17 @@ describe('VariablesUnknownTable', () => { }); describe('when expanding the section', () => { - it('then it should show loading spinner', async () => { - await getTestContext(); - - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); - await waitFor(() => expect(screen.getByText('Loading...')).toBeInTheDocument()); - }); - it('then it should call getUnknownsNetwork', async () => { const { getUnknownsNetworkSpy } = await getTestContext(); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); await waitFor(() => expect(getUnknownsNetworkSpy).toHaveBeenCalledTimes(1)); }); it('then it should report the interaction', async () => { const { reportInteractionSpy } = await getTestContext(); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); - await waitFor(() => expect(screen.getByText('Loading...')).toBeInTheDocument()); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); expect(reportInteractionSpy).toHaveBeenCalledTimes(1); expect(reportInteractionSpy).toHaveBeenCalledWith('Unknown variables section expanded'); @@ -64,14 +63,14 @@ describe('VariablesUnknownTable', () => { it('then it should not call getUnknownsNetwork', async () => { const { getUnknownsNetworkSpy } = await getTestContext(); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); await waitFor(() => expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true')); expect(getUnknownsNetworkSpy).toHaveBeenCalledTimes(1); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); await waitFor(() => expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false')); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); await waitFor(() => expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true')); expect(getUnknownsNetworkSpy).toHaveBeenCalledTimes(1); @@ -82,8 +81,7 @@ describe('VariablesUnknownTable', () => { it('then it should render the correct message', async () => { await getTestContext(); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); - await waitForElementToBeRemoved(() => screen.getByText('Loading...')); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); expect(screen.getByText('No renamed or missing variables found.')).toBeInTheDocument(); }); @@ -95,8 +93,7 @@ describe('VariablesUnknownTable', () => { const usages = [{ variable, nodes: [], edges: [], showGraph: false }]; const { reportInteractionSpy } = await getTestContext({}, usages); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); - await waitForElementToBeRemoved(() => screen.getByText('Loading...')); + await userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); expect(screen.queryByText('No renamed or missing variables found.')).not.toBeInTheDocument(); expect(screen.getByText('Renamed Variable')).toBeInTheDocument(); @@ -108,28 +105,41 @@ describe('VariablesUnknownTable', () => { }); describe('but when the unknown processing takes a while', () => { - const origDateNow = Date.now; + let user: UserEvent; + + beforeEach(() => { + jest.useFakeTimers(); + // Need to use delay: null here to work with fakeTimers + // see https://github.com/testing-library/user-event/issues/833 + user = userEvent.setup({ delay: null }); + }); afterEach(() => { - Date.now = origDateNow; + jest.useRealTimers(); }); it('then it should report slow expansion', async () => { const variable = customBuilder().withId('Renamed Variable').withName('Renamed Variable').build(); const usages = [{ variable, nodes: [], edges: [], showGraph: false }]; - const { reportInteractionSpy } = await getTestContext({}, usages); - const dateNowStart = 1000; - const dateNowStop = 2000; - Date.now = jest.fn().mockReturnValueOnce(dateNowStart).mockReturnValue(dateNowStop); + const { getUnknownsNetworkSpy, reportInteractionSpy } = await getTestContext({}, usages); + getUnknownsNetworkSpy.mockImplementation(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(usages); + }, SLOW_VARIABLES_EXPANSION_THRESHOLD); + }); + }); - userEvent.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); - await waitForElementToBeRemoved(() => screen.getByText('Loading...')); + await user.click(screen.getByRole('heading', { name: /renamed or missing variables/i })); + + jest.advanceTimersByTime(SLOW_VARIABLES_EXPANSION_THRESHOLD); // make sure we report the interaction for slow expansion - expect(reportInteractionSpy).toHaveBeenCalledTimes(2); - expect(reportInteractionSpy.mock.calls[0][0]).toEqual('Unknown variables section expanded'); - expect(reportInteractionSpy.mock.calls[1][0]).toEqual('Slow unknown variables expansion'); - expect(reportInteractionSpy.mock.calls[1][1]).toEqual({ elapsed: 1000 }); + await waitFor(() => + expect(reportInteractionSpy).toHaveBeenCalledWith('Slow unknown variables expansion', { + elapsed: expect.any(Number), + }) + ); }); }); }); diff --git a/public/app/features/variables/inspect/VariablesUnknownTable.tsx b/public/app/features/variables/inspect/VariablesUnknownTable.tsx index 5c45b6a5b88..d9ef1157f24 100644 --- a/public/app/features/variables/inspect/VariablesUnknownTable.tsx +++ b/public/app/features/variables/inspect/VariablesUnknownTable.tsx @@ -1,12 +1,14 @@ -import React, { ReactElement, useEffect, useState } from 'react'; import { css } from '@emotion/css'; +import React, { ReactElement, useEffect, useState } from 'react'; import { useAsync } from 'react-use'; -import { CollapsableSection, HorizontalGroup, Icon, Spinner, Tooltip, useStyles, VerticalGroup } from '@grafana/ui'; + import { GrafanaTheme } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; +import { CollapsableSection, HorizontalGroup, Icon, Spinner, Tooltip, useStyles, VerticalGroup } from '@grafana/ui'; -import { VariableModel } from '../types'; import { DashboardModel } from '../../dashboard/state'; +import { VariableModel } from '../types'; + import { VariablesUnknownButton } from './VariablesUnknownButton'; import { getUnknownsNetwork, UsagesToNetwork } from './utils'; diff --git a/public/app/features/variables/inspect/reducer.test.ts b/public/app/features/variables/inspect/reducer.test.ts index 75218bcb366..7d2a7c6c2d6 100644 --- a/public/app/features/variables/inspect/reducer.test.ts +++ b/public/app/features/variables/inspect/reducer.test.ts @@ -1,7 +1,8 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { initialVariableInspectState, initInspect, variableInspectReducer, VariableInspectState } from './reducer'; import { textboxBuilder } from '../shared/testing/builders'; +import { initialVariableInspectState, initInspect, variableInspectReducer, VariableInspectState } from './reducer'; + describe('variableInspectReducer', () => { describe('when initInspect is dispatched', () => { it('then state should be correct', () => { diff --git a/public/app/features/variables/inspect/reducer.ts b/public/app/features/variables/inspect/reducer.ts index 8ec775d4d1a..be8282edb22 100644 --- a/public/app/features/variables/inspect/reducer.ts +++ b/public/app/features/variables/inspect/reducer.ts @@ -1,6 +1,7 @@ -import { UsagesToNetwork, VariableUsageTree } from './utils'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { UsagesToNetwork, VariableUsageTree } from './utils'; + export interface VariableInspectState { usages: VariableUsageTree[]; usagesNetwork: UsagesToNetwork[]; diff --git a/public/app/features/variables/inspect/utils.test.ts b/public/app/features/variables/inspect/utils.test.ts index 4b2f6167de4..c313f6ef6a0 100644 --- a/public/app/features/variables/inspect/utils.test.ts +++ b/public/app/features/variables/inspect/utils.test.ts @@ -1,3 +1,10 @@ +import { PanelModel } from 'app/features/dashboard/state'; + +import { variableAdapters } from '../adapters'; +import { createCustomVariableAdapter } from '../custom/adapter'; +import { createDataSourceVariableAdapter } from '../datasource/adapter'; +import { createQueryVariableAdapter } from '../query/adapter'; + import { flattenPanels, getAffectedPanelIdsForVariable, @@ -5,11 +12,6 @@ import { getDependenciesForVariable, getPropsWithVariable, } from './utils'; -import { variableAdapters } from '../adapters'; -import { createDataSourceVariableAdapter } from '../datasource/adapter'; -import { createCustomVariableAdapter } from '../custom/adapter'; -import { createQueryVariableAdapter } from '../query/adapter'; -import { PanelModel } from 'app/features/dashboard/state'; describe('getPropsWithVariable', () => { it('when called it should return the correct graph', () => { diff --git a/public/app/features/variables/inspect/utils.ts b/public/app/features/variables/inspect/utils.ts index 3eb5e2affd6..36afa0e2cf4 100644 --- a/public/app/features/variables/inspect/utils.ts +++ b/public/app/features/variables/inspect/utils.ts @@ -1,10 +1,11 @@ -import { variableAdapters } from '../adapters'; -import { DashboardModel, PanelModel } from '../../dashboard/state'; -import { isAdHoc } from '../guard'; +import { DataLinkBuiltInVars } from '@grafana/data'; + import { safeStringifyValue } from '../../../core/utils/explore'; +import { DashboardModel, PanelModel } from '../../dashboard/state'; +import { variableAdapters } from '../adapters'; +import { isAdHoc } from '../guard'; import { VariableModel } from '../types'; import { containsVariable, variableRegex, variableRegexExec } from '../utils'; -import { DataLinkBuiltInVars } from '@grafana/data'; export interface GraphNode { id: string; diff --git a/public/app/features/variables/interval/IntervalVariableEditor.tsx b/public/app/features/variables/interval/IntervalVariableEditor.tsx index 4c12a7960fd..717ec2a7997 100644 --- a/public/app/features/variables/interval/IntervalVariableEditor.tsx +++ b/public/app/features/variables/interval/IntervalVariableEditor.tsx @@ -1,14 +1,15 @@ import React, { ChangeEvent, FormEvent, PureComponent } from 'react'; -import { InlineFieldRow, VerticalGroup } from '@grafana/ui'; -import { IntervalVariableModel } from '../types'; -import { VariableEditorProps } from '../editor/types'; -import { VariableSectionHeader } from '../editor/VariableSectionHeader'; -import { VariableTextField } from '../editor/VariableTextField'; -import { VariableSwitchField } from '../editor/VariableSwitchField'; -import { VariableSelectField } from '../editor/VariableSelectField'; import { SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { InlineFieldRow, VerticalGroup } from '@grafana/ui'; + +import { VariableSectionHeader } from '../editor/VariableSectionHeader'; +import { VariableSelectField } from '../editor/VariableSelectField'; +import { VariableSwitchField } from '../editor/VariableSwitchField'; +import { VariableTextField } from '../editor/VariableTextField'; +import { VariableEditorProps } from '../editor/types'; +import { IntervalVariableModel } from '../types'; export interface Props extends VariableEditorProps {} diff --git a/public/app/features/variables/interval/actions.test.ts b/public/app/features/variables/interval/actions.test.ts index 007caacc7a7..6a8b4020b79 100644 --- a/public/app/features/variables/interval/actions.test.ts +++ b/public/app/features/variables/interval/actions.test.ts @@ -1,27 +1,29 @@ -import { getRootReducer, RootReducerType } from '../state/helpers'; +import { dateTime } from '@grafana/data'; + import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { updateAutoValue, UpdateAutoValueDependencies, updateIntervalVariableOptions } from './actions'; -import { createIntervalOptions } from './reducer'; +import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import { afterEach, beforeEach } from '../../../../test/lib/common'; +import { notifyApp } from '../../../core/actions'; +import { getTimeSrv, setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv'; +import { TemplateSrv } from '../../templating/template_srv'; +import { variableAdapters } from '../adapters'; +import { intervalBuilder } from '../shared/testing/builders'; +import { updateOptions } from '../state/actions'; +import { getRootReducer, RootReducerType } from '../state/helpers'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; import { addVariable, setCurrentVariableValue, variableStateFailed, variableStateFetching, } from '../state/sharedReducer'; -import { variableAdapters } from '../adapters'; -import { createIntervalVariableAdapter } from './adapter'; -import { dateTime } from '@grafana/data'; -import { getTimeSrv, setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv'; -import { TemplateSrv } from '../../templating/template_srv'; -import { intervalBuilder } from '../shared/testing/builders'; -import { updateOptions } from '../state/actions'; -import { notifyApp } from '../../../core/actions'; -import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; import { variablesInitTransaction } from '../state/transactionReducer'; -import { afterEach, beforeEach } from '../../../../test/lib/common'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; +import { updateAutoValue, UpdateAutoValueDependencies, updateIntervalVariableOptions } from './actions'; +import { createIntervalVariableAdapter } from './adapter'; +import { createIntervalOptions } from './reducer'; + describe('interval actions', () => { variableAdapters.setInit(() => [createIntervalVariableAdapter()]); describe('when updateIntervalVariableOptions is dispatched', () => { diff --git a/public/app/features/variables/interval/actions.ts b/public/app/features/variables/interval/actions.ts index bc3f4d8d521..24565a1fea2 100644 --- a/public/app/features/variables/interval/actions.ts +++ b/public/app/features/variables/interval/actions.ts @@ -1,16 +1,17 @@ import { rangeUtil } from '@grafana/data'; -import { KeyedVariableIdentifier } from '../state/types'; import { ThunkResult } from '../../../types'; -import { createIntervalOptions } from './reducer'; -import { validateVariableSelectionState } from '../state/actions'; -import { getVariable } from '../state/selectors'; -import { IntervalVariableModel } from '../types'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; import { getTemplateSrv, TemplateSrv } from '../../templating/template_srv'; +import { validateVariableSelectionState } from '../state/actions'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { getVariable } from '../state/selectors'; +import { KeyedVariableIdentifier } from '../state/types'; +import { IntervalVariableModel } from '../types'; import { toVariablePayload } from '../utils'; +import { createIntervalOptions } from './reducer'; + export const updateIntervalVariableOptions = (identifier: KeyedVariableIdentifier): ThunkResult => async (dispatch) => { diff --git a/public/app/features/variables/interval/adapter.ts b/public/app/features/variables/interval/adapter.ts index 69e523836a6..a88a83b455c 100644 --- a/public/app/features/variables/interval/adapter.ts +++ b/public/app/features/variables/interval/adapter.ts @@ -1,13 +1,15 @@ import { cloneDeep } from 'lodash'; -import { IntervalVariableModel } from '../types'; + import { dispatch } from '../../../store/store'; -import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; -import { initialIntervalVariableModelState, intervalVariableReducer } from './reducer'; +import { optionPickerFactory } from '../pickers'; +import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; +import { IntervalVariableModel } from '../types'; +import { toKeyedVariableIdentifier } from '../utils'; + import { IntervalVariableEditor } from './IntervalVariableEditor'; import { updateAutoValue, updateIntervalVariableOptions } from './actions'; -import { optionPickerFactory } from '../pickers'; -import { toKeyedVariableIdentifier } from '../utils'; +import { initialIntervalVariableModelState, intervalVariableReducer } from './reducer'; export const createIntervalVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/interval/reducer.test.ts b/public/app/features/variables/interval/reducer.test.ts index e5b04df6ae8..c3542c2322b 100644 --- a/public/app/features/variables/interval/reducer.test.ts +++ b/public/app/features/variables/interval/reducer.test.ts @@ -1,13 +1,14 @@ import { cloneDeep } from 'lodash'; +import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { getVariableTestContext } from '../state/helpers'; import { VariablesState } from '../state/types'; -import { createIntervalVariableAdapter } from './adapter'; import { IntervalVariableModel } from '../types'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { createIntervalOptions, intervalVariableReducer } from './reducer'; import { toVariablePayload } from '../utils'; +import { createIntervalVariableAdapter } from './adapter'; +import { createIntervalOptions, intervalVariableReducer } from './reducer'; + describe('intervalVariableReducer', () => { const adapter = createIntervalVariableAdapter(); describe('when createIntervalOptions is dispatched', () => { diff --git a/public/app/features/variables/interval/reducer.ts b/public/app/features/variables/interval/reducer.ts index faadce99a6a..d1e910bd7f2 100644 --- a/public/app/features/variables/interval/reducer.ts +++ b/public/app/features/variables/interval/reducer.ts @@ -1,8 +1,9 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { initialVariableModelState, IntervalVariableModel, VariableOption, VariableRefresh } from '../types'; -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; import { map } from 'lodash'; + import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; +import { initialVariableModelState, IntervalVariableModel, VariableOption, VariableRefresh } from '../types'; export const initialIntervalVariableModelState: IntervalVariableModel = { ...initialVariableModelState, diff --git a/public/app/features/variables/pickers/OptionsPicker/OptionPicker.test.tsx b/public/app/features/variables/pickers/OptionsPicker/OptionPicker.test.tsx index 36a1a50e17e..60b42c963dd 100644 --- a/public/app/features/variables/pickers/OptionsPicker/OptionPicker.test.tsx +++ b/public/app/features/variables/pickers/OptionsPicker/OptionPicker.test.tsx @@ -1,16 +1,18 @@ -import React from 'react'; -import { Provider } from 'react-redux'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Provider } from 'react-redux'; + +import { LoadingState } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { LoadingState } from '@grafana/data'; -import { VariablePickerProps } from '../types'; -import { QueryVariableModel, VariableWithMultiSupport, VariableWithOptions } from '../../types'; import { queryBuilder } from '../../shared/testing/builders'; +import { getPreloadedState } from '../../state/helpers'; +import { QueryVariableModel, VariableWithMultiSupport, VariableWithOptions } from '../../types'; +import { VariablePickerProps } from '../types'; + import { optionPickerFactory } from './OptionsPicker'; import { initialOptionPickerState, OptionsPickerState } from './reducer'; -import { getPreloadedState } from '../../state/helpers'; interface Args { pickerState?: Partial; @@ -71,11 +73,11 @@ describe('OptionPicker', () => { expect(getSubMenu('A + C')).toBeInTheDocument(); }); - it('link text should be clickable', () => { + it('link text should be clickable', async () => { const { dispatch } = setupTestContext(); dispatch.mockClear(); - userEvent.click(getSubMenu('A + C')); + await userEvent.click(getSubMenu('A + C')); expect(dispatch).toHaveBeenCalledTimes(1); }); }); @@ -89,14 +91,14 @@ describe('OptionPicker', () => { expect(getSubMenu('A + C')).toBeInTheDocument(); }); - it('link text should be clickable', () => { + it('link text should be clickable', async () => { const { dispatch } = setupTestContext({ variable: defaultVariable, pickerState: { id: 'Other' }, }); dispatch.mockClear(); - userEvent.click(getSubMenu('A + C')); + await userEvent.click(getSubMenu('A + C')); expect(dispatch).toHaveBeenCalledTimes(1); }); }); @@ -110,13 +112,13 @@ describe('OptionPicker', () => { expect(screen.getByLabelText(selectors.components.LoadingIndicator.icon)).toBeInTheDocument(); }); - it('link text should not be clickable', () => { + it('link text should not be clickable', async () => { const { dispatch } = setupTestContext({ variable: { ...defaultVariable, state: LoadingState.Loading }, }); dispatch.mockClear(); - userEvent.click(getSubMenu('A + C')); + await userEvent.click(getSubMenu('A + C')); expect(dispatch).toHaveBeenCalledTimes(0); }); }); diff --git a/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx b/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx index c33a9afc05b..aad1ab98e0f 100644 --- a/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx +++ b/public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx @@ -1,24 +1,26 @@ import React, { ComponentType, PureComponent } from 'react'; -import { bindActionCreators } from 'redux'; import { connect, ConnectedProps } from 'react-redux'; -import { ClickOutsideWrapper } from '@grafana/ui'; -import { LoadingState } from '@grafana/data'; +import { bindActionCreators } from 'redux'; +import { LoadingState } from '@grafana/data'; +import { ClickOutsideWrapper } from '@grafana/ui'; import { StoreState, ThunkDispatch } from 'app/types'; + +import { isMulti } from '../../guard'; +import { getVariableQueryRunner } from '../../query/VariableQueryRunner'; +import { formatVariableLabel } from '../../shared/formatVariable'; +import { toKeyedAction } from '../../state/keyedVariablesReducer'; +import { getVariablesState } from '../../state/selectors'; +import { KeyedVariableIdentifier } from '../../state/types'; +import { VariableOption, VariableWithMultiSupport, VariableWithOptions } from '../../types'; +import { toKeyedVariableIdentifier } from '../../utils'; import { VariableInput } from '../shared/VariableInput'; +import { VariableLink } from '../shared/VariableLink'; +import { VariableOptions } from '../shared/VariableOptions'; +import { NavigationKey, VariablePickerProps } from '../types'; + import { commitChangesToVariable, filterOrSearchOptions, navigateOptions, openOptions } from './actions'; import { initialOptionPickerState, OptionsPickerState, toggleAllOptions, toggleOption } from './reducer'; -import { VariableOption, VariableWithMultiSupport, VariableWithOptions } from '../../types'; -import { VariableOptions } from '../shared/VariableOptions'; -import { isMulti } from '../../guard'; -import { NavigationKey, VariablePickerProps } from '../types'; -import { formatVariableLabel } from '../../shared/formatVariable'; -import { KeyedVariableIdentifier } from '../../state/types'; -import { getVariableQueryRunner } from '../../query/VariableQueryRunner'; -import { VariableLink } from '../shared/VariableLink'; -import { getVariablesState } from '../../state/selectors'; -import { toKeyedAction } from '../../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier } from '../../utils'; export const optionPickerFactory = (): ComponentType< VariablePickerProps diff --git a/public/app/features/variables/pickers/OptionsPicker/actions.test.ts b/public/app/features/variables/pickers/OptionsPicker/actions.test.ts index 0c5e4ecffc3..c578748bfc4 100644 --- a/public/app/features/variables/pickers/OptionsPicker/actions.test.ts +++ b/public/app/features/variables/pickers/OptionsPicker/actions.test.ts @@ -1,6 +1,23 @@ +import { locationService } from '@grafana/runtime'; + import { reduxTester } from '../../../../../test/core/redux/reduxTester'; +import { variableAdapters } from '../../adapters'; +import { createQueryVariableAdapter } from '../../query/adapter'; +import { queryBuilder } from '../../shared/testing/builders'; import { getPreloadedState, getRootReducer, RootReducerType } from '../../state/helpers'; +import { toKeyedAction } from '../../state/keyedVariablesReducer'; +import { addVariable, changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; import { initialVariableModelState, QueryVariableModel, VariableRefresh, VariableSort } from '../../types'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../../utils'; +import { NavigationKey } from '../types'; + +import { + commitChangesToVariable, + filterOrSearchOptions, + navigateOptions, + openOptions, + toggleOptionByHighlight, +} from './actions'; import { hideOptions, initialOptionPickerState, @@ -10,21 +27,6 @@ import { updateOptionsAndFilter, updateSearchQuery, } from './reducer'; -import { - commitChangesToVariable, - filterOrSearchOptions, - navigateOptions, - openOptions, - toggleOptionByHighlight, -} from './actions'; -import { NavigationKey } from '../types'; -import { addVariable, changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; -import { variableAdapters } from '../../adapters'; -import { createQueryVariableAdapter } from '../../query/adapter'; -import { locationService } from '@grafana/runtime'; -import { queryBuilder } from '../../shared/testing/builders'; -import { toKeyedAction } from '../../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../../utils'; const datasource = { metricFindQuery: jest.fn(() => Promise.resolve([])), diff --git a/public/app/features/variables/pickers/OptionsPicker/actions.ts b/public/app/features/variables/pickers/OptionsPicker/actions.ts index 7d25290fb22..04f604f9fd5 100644 --- a/public/app/features/variables/pickers/OptionsPicker/actions.ts +++ b/public/app/features/variables/pickers/OptionsPicker/actions.ts @@ -1,9 +1,16 @@ import { debounce, trim } from 'lodash'; + import { StoreState, ThunkDispatch, ThunkResult } from 'app/types'; -import { VariableOption, VariableWithMultiSupport, VariableWithOptions } from '../../types'; + import { variableAdapters } from '../../adapters'; +import { toKeyedAction } from '../../state/keyedVariablesReducer'; import { getVariable, getVariablesState } from '../../state/selectors'; +import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; +import { KeyedVariableIdentifier } from '../../state/types'; +import { VariableOption, VariableWithMultiSupport, VariableWithOptions } from '../../types'; +import { containsSearchFilter, getCurrentText, toVariablePayload } from '../../utils'; import { NavigationKey } from '../types'; + import { hideOptions, moveOptionsHighlight, @@ -14,10 +21,6 @@ import { updateOptionsFromSearch, updateSearchQuery, } from './reducer'; -import { changeVariableProp, setCurrentVariableValue } from '../../state/sharedReducer'; -import { KeyedVariableIdentifier } from '../../state/types'; -import { containsSearchFilter, getCurrentText, toVariablePayload } from '../../utils'; -import { toKeyedAction } from '../../state/keyedVariablesReducer'; export const navigateOptions = (rootStateKey: string, key: NavigationKey, clearOthers: boolean): ThunkResult => { return async (dispatch, getState) => { diff --git a/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts b/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts index b706a6c3a83..473d70a78c6 100644 --- a/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts +++ b/public/app/features/variables/pickers/OptionsPicker/reducer.test.ts @@ -1,4 +1,9 @@ import { cloneDeep } from 'lodash'; + +import { reducerTester } from '../../../../../test/core/redux/reducerTester'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../../constants'; +import { QueryVariableModel, VariableOption } from '../../types'; + import { cleanPickerState, hideOptions, @@ -14,9 +19,6 @@ import { updateOptionsFromSearch, updateSearchQuery, } from './reducer'; -import { reducerTester } from '../../../../../test/core/redux/reducerTester'; -import { QueryVariableModel, VariableOption } from '../../types'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../../constants'; const getVariableTestContext = (extend: Partial) => { return { diff --git a/public/app/features/variables/pickers/OptionsPicker/reducer.ts b/public/app/features/variables/pickers/OptionsPicker/reducer.ts index ce9f009f384..64461273399 100644 --- a/public/app/features/variables/pickers/OptionsPicker/reducer.ts +++ b/public/app/features/variables/pickers/OptionsPicker/reducer.ts @@ -1,10 +1,11 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { cloneDeep, isString, trim } from 'lodash'; -import { VariableOption, VariableWithOptions } from '../../types'; -import { isMulti, isQuery } from '../../guard'; + import { applyStateChanges } from '../../../../core/utils/applyStateChanges'; -import { containsSearchFilter } from '../../utils'; import { ALL_VARIABLE_VALUE } from '../../constants'; +import { isMulti, isQuery } from '../../guard'; +import { VariableOption, VariableWithOptions } from '../../types'; +import { containsSearchFilter } from '../../utils'; export interface ToggleOption { option?: VariableOption; diff --git a/public/app/features/variables/pickers/PickerRenderer.tsx b/public/app/features/variables/pickers/PickerRenderer.tsx index 7defe8ceb0b..80c2a73a5df 100644 --- a/public/app/features/variables/pickers/PickerRenderer.tsx +++ b/public/app/features/variables/pickers/PickerRenderer.tsx @@ -1,9 +1,11 @@ import React, { FunctionComponent, PropsWithChildren, ReactElement, useMemo } from 'react'; -import { VariableHide, VariableModel } from '../types'; + import { selectors } from '@grafana/e2e-selectors'; -import { variableAdapters } from '../adapters'; import { Tooltip } from '@grafana/ui'; +import { variableAdapters } from '../adapters'; +import { VariableHide, VariableModel } from '../types'; + interface Props { variable: VariableModel; } diff --git a/public/app/features/variables/pickers/shared/VariableInput.tsx b/public/app/features/variables/pickers/shared/VariableInput.tsx index ad9c57a5a94..967015709db 100644 --- a/public/app/features/variables/pickers/shared/VariableInput.tsx +++ b/public/app/features/variables/pickers/shared/VariableInput.tsx @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; + import { NavigationKey } from '../types'; export interface Props extends Omit, 'onChange' | 'value'> { diff --git a/public/app/features/variables/pickers/shared/VariableLink.tsx b/public/app/features/variables/pickers/shared/VariableLink.tsx index e50fda6d2a5..dcdf06f73b9 100644 --- a/public/app/features/variables/pickers/shared/VariableLink.tsx +++ b/public/app/features/variables/pickers/shared/VariableLink.tsx @@ -1,8 +1,9 @@ -import React, { FC, MouseEvent, useCallback } from 'react'; import { css } from '@emotion/css'; -import { Icon, Tooltip, useStyles } from '@grafana/ui'; -import { selectors } from '@grafana/e2e-selectors'; +import React, { FC, MouseEvent, useCallback } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { Icon, Tooltip, useStyles } from '@grafana/ui'; interface Props { onClick: () => void; diff --git a/public/app/features/variables/pickers/shared/VariableOptions.tsx b/public/app/features/variables/pickers/shared/VariableOptions.tsx index e3f77b863de..3a4e96e05fc 100644 --- a/public/app/features/variables/pickers/shared/VariableOptions.tsx +++ b/public/app/features/variables/pickers/shared/VariableOptions.tsx @@ -1,9 +1,10 @@ +import { css, cx } from '@emotion/css'; import React, { PureComponent } from 'react'; -import { Tooltip } from '@grafana/ui'; + import { selectors } from '@grafana/e2e-selectors'; +import { Tooltip } from '@grafana/ui'; import { VariableOption } from '../../types'; -import { css, cx } from '@emotion/css'; export interface Props extends React.HTMLProps { multi: boolean; diff --git a/public/app/features/variables/query/QueryVariableEditor.test.tsx b/public/app/features/variables/query/QueryVariableEditor.test.tsx index 987446dd904..23754e21007 100644 --- a/public/app/features/variables/query/QueryVariableEditor.test.tsx +++ b/public/app/features/variables/query/QueryVariableEditor.test.tsx @@ -1,17 +1,19 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { DataSourceApi } from '@grafana/data'; +import { mockDataSource } from 'app/features/alerting/unified/mocks'; +import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; + +import { describe, expect } from '../../../../test/lib/common'; +import { NEW_VARIABLE_ID } from '../constants'; +import { LegacyVariableQueryEditor } from '../editor/LegacyVariableQueryEditor'; +import { KeyedVariableIdentifier } from '../state/types'; +import { VariableModel } from '../types'; import { Props, QueryVariableEditorUnConnected } from './QueryVariableEditor'; import { initialQueryVariableModelState } from './reducer'; -import { describe, expect } from '../../../../test/lib/common'; -import { LegacyVariableQueryEditor } from '../editor/LegacyVariableQueryEditor'; -import { mockDataSource } from 'app/features/alerting/unified/mocks'; -import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; -import { NEW_VARIABLE_ID } from '../constants'; -import { VariableModel } from '../types'; -import { KeyedVariableIdentifier } from '../state/types'; const setupTestContext = (options: Partial) => { const variableDefaults: Partial = { rootStateKey: 'key' }; @@ -69,13 +71,13 @@ describe('QueryVariableEditor', () => { ${'regex'} | ${'onPropChange'} | ${[{ propName: 'regex', propValue: 't', updateOptions: true }]} `( '$fieldName field and tabs away then $propName should be called with correct args', - ({ fieldName, propName, expectedArgs }) => { + async ({ fieldName, propName, expectedArgs }) => { const { props } = setupTestContext({}); const propUnderTest = props[propName]; const fieldAccessor = fieldAccessors[fieldName]; - userEvent.type(fieldAccessor(), 't'); - userEvent.tab(); + await userEvent.type(fieldAccessor(), 't'); + await userEvent.tab(); expect(propUnderTest).toHaveBeenCalledTimes(1); expect(propUnderTest).toHaveBeenCalledWith(...expectedArgs); @@ -90,14 +92,14 @@ describe('QueryVariableEditor', () => { ${'regex'} | ${'onPropChange'} `( '$fieldName field but reverts the change and tabs away then $propName should not be called', - ({ fieldName, propName }) => { + async ({ fieldName, propName }) => { const { props } = setupTestContext({}); const propUnderTest = props[propName]; const fieldAccessor = fieldAccessors[fieldName]; - userEvent.type(fieldAccessor(), 't'); - userEvent.type(fieldAccessor(), '{backspace}'); - userEvent.tab(); + await userEvent.type(fieldAccessor(), 't'); + await userEvent.type(fieldAccessor(), '{backspace}'); + await userEvent.tab(); expect(propUnderTest).not.toHaveBeenCalled(); } diff --git a/public/app/features/variables/query/QueryVariableEditor.tsx b/public/app/features/variables/query/QueryVariableEditor.tsx index 94ff3cb539b..d407e965d9c 100644 --- a/public/app/features/variables/query/QueryVariableEditor.tsx +++ b/public/app/features/variables/query/QueryVariableEditor.tsx @@ -1,27 +1,29 @@ +import { css } from '@emotion/css'; import React, { FormEvent, PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { css } from '@emotion/css'; -import { InlineField, InlineFieldRow, VerticalGroup } from '@grafana/ui'; + +import { DataSourceInstanceSettings, getDataSourceRef, LoadingState, SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { DataSourcePicker, getTemplateSrv } from '@grafana/runtime'; -import { DataSourceInstanceSettings, getDataSourceRef, LoadingState, SelectableValue } from '@grafana/data'; +import { InlineField, InlineFieldRow, VerticalGroup } from '@grafana/ui'; -import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; -import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types'; -import { changeQueryVariableDataSource, changeQueryVariableQuery, initQueryVariableEditor } from './actions'; -import { initialVariableEditorState } from '../editor/reducer'; -import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; import { StoreState } from '../../../types'; -import { changeVariableMultiValue } from '../state/actions'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; -import { isLegacyQueryEditor, isQueryEditor } from '../guard'; +import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor'; import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableTextField } from '../editor/VariableTextField'; +import { initialVariableEditorState } from '../editor/reducer'; +import { getQueryVariableEditorState } from '../editor/selectors'; +import { OnPropChangeArguments, VariableEditorProps } from '../editor/types'; +import { isLegacyQueryEditor, isQueryEditor } from '../guard'; +import { changeVariableMultiValue } from '../state/actions'; +import { getVariablesState } from '../state/selectors'; +import { QueryVariableModel, VariableRefresh, VariableSort, VariableWithMultiSupport } from '../types'; +import { toKeyedVariableIdentifier } from '../utils'; + import { QueryVariableRefreshSelect } from './QueryVariableRefreshSelect'; import { QueryVariableSortSelect } from './QueryVariableSortSelect'; -import { getQueryVariableEditorState } from '../editor/selectors'; -import { getVariablesState } from '../state/selectors'; -import { toKeyedVariableIdentifier } from '../utils'; +import { changeQueryVariableDataSource, changeQueryVariableQuery, initQueryVariableEditor } from './actions'; const mapStateToProps = (state: StoreState, ownProps: OwnProps) => { const { rootStateKey } = ownProps.variable; diff --git a/public/app/features/variables/query/QueryVariableRefreshSelect.tsx b/public/app/features/variables/query/QueryVariableRefreshSelect.tsx index 4bb6d7953e4..ed77552852b 100644 --- a/public/app/features/variables/query/QueryVariableRefreshSelect.tsx +++ b/public/app/features/variables/query/QueryVariableRefreshSelect.tsx @@ -1,6 +1,8 @@ import React, { PropsWithChildren, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; + import { VariableSelectField } from '../editor/VariableSelectField'; import { VariableRefresh } from '../types'; diff --git a/public/app/features/variables/query/QueryVariableSortSelect.tsx b/public/app/features/variables/query/QueryVariableSortSelect.tsx index 6ab3052ceb5..d097f460c62 100644 --- a/public/app/features/variables/query/QueryVariableSortSelect.tsx +++ b/public/app/features/variables/query/QueryVariableSortSelect.tsx @@ -1,6 +1,8 @@ import React, { PropsWithChildren, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; + import { VariableSelectField } from '../editor/VariableSelectField'; import { VariableSort } from '../types'; diff --git a/public/app/features/variables/query/VariableQueryRunner.test.ts b/public/app/features/variables/query/VariableQueryRunner.test.ts index e1670479de2..223da0473c1 100644 --- a/public/app/features/variables/query/VariableQueryRunner.test.ts +++ b/public/app/features/variables/query/VariableQueryRunner.test.ts @@ -1,18 +1,20 @@ import { of, throwError } from 'rxjs'; -import { getDefaultTimeRange, LoadingState, VariableSupportType } from '@grafana/data'; import { delay } from 'rxjs/operators'; -import { UpdateOptionsResults, VariableQueryRunner } from './VariableQueryRunner'; +import { getDefaultTimeRange, LoadingState, VariableSupportType } from '@grafana/data'; + import { queryBuilder } from '../shared/testing/builders'; -import { QueryRunner, QueryRunners } from './queryRunners'; -import { KeyedVariableIdentifier } from '../state/types'; -import { QueryVariableModel } from '../types'; -import { updateVariableOptions } from './reducer'; +import { getPreloadedState } from '../state/helpers'; import { toKeyedAction } from '../state/keyedVariablesReducer'; import { initialTransactionState } from '../state/transactionReducer'; -import { getPreloadedState } from '../state/helpers'; +import { KeyedVariableIdentifier } from '../state/types'; +import { QueryVariableModel } from '../types'; import { toKeyedVariableIdentifier } from '../utils'; +import { UpdateOptionsResults, VariableQueryRunner } from './VariableQueryRunner'; +import { QueryRunner, QueryRunners } from './queryRunners'; +import { updateVariableOptions } from './reducer'; + type DoneCallback = { (...args: any[]): any; fail(error?: string | { message: string }): any; diff --git a/public/app/features/variables/query/VariableQueryRunner.ts b/public/app/features/variables/query/VariableQueryRunner.ts index 866cc92ca85..7b33e615cd2 100644 --- a/public/app/features/variables/query/VariableQueryRunner.ts +++ b/public/app/features/variables/query/VariableQueryRunner.ts @@ -1,5 +1,7 @@ import { merge, Observable, of, Subject, throwError, Unsubscribable } from 'rxjs'; import { catchError, filter, finalize, mergeMap, take, takeUntil } from 'rxjs/operators'; +import { v4 as uuidv4 } from 'uuid'; + import { CoreApp, DataQuery, @@ -11,17 +13,17 @@ import { ScopedVars, } from '@grafana/data'; -import { KeyedVariableIdentifier } from '../state/types'; -import { getLastKey, getVariable } from '../state/selectors'; -import { QueryVariableModel, VariableRefresh } from '../types'; -import { StoreState, ThunkDispatch } from '../../../types'; import { dispatch, getState } from '../../../store/store'; -import { getTemplatedRegex } from '../utils'; -import { v4 as uuidv4 } from 'uuid'; +import { StoreState, ThunkDispatch } from '../../../types'; import { getTimeSrv } from '../../dashboard/services/TimeSrv'; -import { QueryRunners } from './queryRunners'; import { runRequest } from '../../query/state/runRequest'; +import { getLastKey, getVariable } from '../state/selectors'; +import { KeyedVariableIdentifier } from '../state/types'; +import { QueryVariableModel, VariableRefresh } from '../types'; +import { getTemplatedRegex } from '../utils'; + import { toMetricFindValues, updateOptionsState, validateVariableSelection } from './operators'; +import { QueryRunners } from './queryRunners'; interface UpdateOptionsArgs { identifier: KeyedVariableIdentifier; diff --git a/public/app/features/variables/query/actions.test.tsx b/public/app/features/variables/query/actions.test.tsx index 7f8afffcd8b..50ed066034c 100644 --- a/public/app/features/variables/query/actions.test.tsx +++ b/public/app/features/variables/query/actions.test.tsx @@ -1,11 +1,26 @@ import React from 'react'; +import { expect } from 'test/lib/common'; + import { DataSourceRef, getDefaultTimeRange, LoadingState } from '@grafana/data'; +import { setDataSourceSrv } from '@grafana/runtime'; -import { variableAdapters } from '../adapters'; -import { createQueryVariableAdapter } from './adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import { notifyApp } from '../../../core/reducers/appNotification'; +import { getTimeSrv, setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv'; +import { variableAdapters } from '../adapters'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; +import { LegacyVariableQueryEditor } from '../editor/LegacyVariableQueryEditor'; +import { + addVariableEditorError, + changeVariableEditorExtended, + initialVariableEditorState, + removeVariableEditorError, + setIdInEditor, +} from '../editor/reducer'; +import { updateOptions } from '../state/actions'; import { getPreloadedState, getRootReducer, RootReducerType } from '../state/helpers'; -import { QueryVariableModel, VariableHide, VariableQueryEditorProps, VariableRefresh, VariableSort } from '../types'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; import { addVariable, changeVariableProp, @@ -14,6 +29,11 @@ import { variableStateFailed, variableStateFetching, } from '../state/sharedReducer'; +import { variablesInitTransaction } from '../state/transactionReducer'; +import { QueryVariableModel, VariableHide, VariableQueryEditorProps, VariableRefresh, VariableSort } from '../types'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + +import { setVariableQueryRunner, VariableQueryRunner } from './VariableQueryRunner'; import { changeQueryVariableDataSource, changeQueryVariableQuery, @@ -22,26 +42,8 @@ import { initQueryVariableEditor, updateQueryVariableOptions, } from './actions'; +import { createQueryVariableAdapter } from './adapter'; import { updateVariableOptions } from './reducer'; -import { - addVariableEditorError, - changeVariableEditorExtended, - initialVariableEditorState, - removeVariableEditorError, - setIdInEditor, -} from '../editor/reducer'; -import { LegacyVariableQueryEditor } from '../editor/LegacyVariableQueryEditor'; -import { expect } from 'test/lib/common'; -import { updateOptions } from '../state/actions'; -import { notifyApp } from '../../../core/reducers/appNotification'; -import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; -import { getTimeSrv, setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv'; -import { setVariableQueryRunner, VariableQueryRunner } from './VariableQueryRunner'; -import { setDataSourceSrv } from '@grafana/runtime'; -import { variablesInitTransaction } from '../state/transactionReducer'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; const mocks: Record = { datasource: { diff --git a/public/app/features/variables/query/actions.ts b/public/app/features/variables/query/actions.ts index 45d42c5fec8..f2aca6b6e59 100644 --- a/public/app/features/variables/query/actions.ts +++ b/public/app/features/variables/query/actions.ts @@ -1,20 +1,22 @@ import { Subscription } from 'rxjs'; -import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime'; -import { DataSourceRef } from '@grafana/data'; -import { updateOptions } from '../state/actions'; -import { QueryVariableModel } from '../types'; +import { DataSourceRef } from '@grafana/data'; +import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime'; + import { ThunkResult } from '../../../types'; -import { getVariable, getVariablesState } from '../state/selectors'; +import { getVariableQueryEditor } from '../editor/getVariableQueryEditor'; import { addVariableEditorError, changeVariableEditorExtended, removeVariableEditorError } from '../editor/reducer'; +import { getQueryVariableEditorState } from '../editor/selectors'; +import { updateOptions } from '../state/actions'; +import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { getVariable, getVariablesState } from '../state/selectors'; import { changeVariableProp } from '../state/sharedReducer'; import { KeyedVariableIdentifier } from '../state/types'; -import { getVariableQueryEditor } from '../editor/getVariableQueryEditor'; +import { QueryVariableModel } from '../types'; +import { hasOngoingTransaction, toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + import { getVariableQueryRunner } from './VariableQueryRunner'; import { variableQueryObserver } from './variableQueryObserver'; -import { hasOngoingTransaction, toKeyedVariableIdentifier, toVariablePayload } from '../utils'; -import { toKeyedAction } from '../state/keyedVariablesReducer'; -import { getQueryVariableEditorState } from '../editor/selectors'; export const updateQueryVariableOptions = ( identifier: KeyedVariableIdentifier, diff --git a/public/app/features/variables/query/adapter.ts b/public/app/features/variables/query/adapter.ts index 06a2442ab69..6c4eb1f7563 100644 --- a/public/app/features/variables/query/adapter.ts +++ b/public/app/features/variables/query/adapter.ts @@ -1,15 +1,16 @@ import { cloneDeep } from 'lodash'; -import { QueryVariableModel, VariableRefresh } from '../types'; -import { initialQueryVariableModelState, queryVariableReducer } from './reducer'; import { dispatch } from '../../../store/store'; -import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; import { VariableAdapter } from '../adapters'; +import { ALL_VARIABLE_TEXT } from '../constants'; +import { optionPickerFactory } from '../pickers'; +import { setOptionAsCurrent, setOptionFromUrl } from '../state/actions'; +import { QueryVariableModel, VariableRefresh } from '../types'; +import { containsVariable, isAllVariable, toKeyedVariableIdentifier } from '../utils'; + import { QueryVariableEditor } from './QueryVariableEditor'; import { updateQueryVariableOptions } from './actions'; -import { containsVariable, isAllVariable, toKeyedVariableIdentifier } from '../utils'; -import { optionPickerFactory } from '../pickers'; -import { ALL_VARIABLE_TEXT } from '../constants'; +import { initialQueryVariableModelState, queryVariableReducer } from './reducer'; export const createQueryVariableAdapter = (): VariableAdapter => { return { diff --git a/public/app/features/variables/query/operators.test.ts b/public/app/features/variables/query/operators.test.ts index ad3cdcce819..9f4fd29d987 100644 --- a/public/app/features/variables/query/operators.test.ts +++ b/public/app/features/variables/query/operators.test.ts @@ -1,10 +1,13 @@ import { of } from 'rxjs'; -import { queryBuilder } from '../shared/testing/builders'; + import { FieldType, toDataFrame } from '@grafana/data'; -import { updateVariableOptions } from './reducer'; -import { areMetricFindValues, toMetricFindValues, updateOptionsState, validateVariableSelection } from './operators'; + +import { queryBuilder } from '../shared/testing/builders'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { areMetricFindValues, toMetricFindValues, updateOptionsState, validateVariableSelection } from './operators'; +import { updateVariableOptions } from './reducer'; + describe('operators', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/public/app/features/variables/query/operators.ts b/public/app/features/variables/query/operators.ts index 2085df4bd23..43df3bed00e 100644 --- a/public/app/features/variables/query/operators.ts +++ b/public/app/features/variables/query/operators.ts @@ -1,14 +1,16 @@ import { from, of, OperatorFunction } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; -import { QueryVariableModel } from '../types'; +import { FieldType, getFieldDisplayName, isDataFrame, MetricFindValue, PanelData } from '@grafana/data'; +import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; + import { ThunkDispatch } from '../../../types'; import { validateVariableSelectionState } from '../state/actions'; -import { FieldType, getFieldDisplayName, isDataFrame, MetricFindValue, PanelData } from '@grafana/data'; -import { updateVariableOptions } from './reducer'; -import { getTemplatedRegex, toKeyedVariableIdentifier, toVariablePayload } from '../utils'; -import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { QueryVariableModel } from '../types'; +import { getTemplatedRegex, toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + +import { updateVariableOptions } from './reducer'; export function toMetricFindValues(): OperatorFunction { return (source) => diff --git a/public/app/features/variables/query/queryRunners.test.ts b/public/app/features/variables/query/queryRunners.test.ts index 066defdd32a..8757209939e 100644 --- a/public/app/features/variables/query/queryRunners.test.ts +++ b/public/app/features/variables/query/queryRunners.test.ts @@ -1,8 +1,11 @@ -import { QueryRunners, variableDummyRefId } from './queryRunners'; -import { getDefaultTimeRange, VariableSupportType } from '@grafana/data'; -import { VariableRefresh } from '../types'; import { of } from 'rxjs'; +import { getDefaultTimeRange, VariableSupportType } from '@grafana/data'; + +import { VariableRefresh } from '../types'; + +import { QueryRunners, variableDummyRefId } from './queryRunners'; + describe('QueryRunners', () => { describe('when using a legacy data source', () => { const getLegacyTestContext = (variable?: any) => { diff --git a/public/app/features/variables/query/queryRunners.ts b/public/app/features/variables/query/queryRunners.ts index 38d7fa60c12..3229bff1eee 100644 --- a/public/app/features/variables/query/queryRunners.ts +++ b/public/app/features/variables/query/queryRunners.ts @@ -1,5 +1,6 @@ import { from, Observable, of } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; + import { DataQuery, DataQueryRequest, @@ -10,15 +11,15 @@ import { VariableSupportType, } from '@grafana/data'; -import { QueryVariableModel } from '../types'; +import { TimeSrv } from '../../dashboard/services/TimeSrv'; import { hasCustomVariableSupport, hasDatasourceVariableSupport, hasLegacyVariableSupport, hasStandardVariableSupport, } from '../guard'; +import { QueryVariableModel } from '../types'; import { getLegacyQueryOptions } from '../utils'; -import { TimeSrv } from '../../dashboard/services/TimeSrv'; export interface RunnerArgs { variable: QueryVariableModel; diff --git a/public/app/features/variables/query/reducer.test.ts b/public/app/features/variables/query/reducer.test.ts index 106f8c50903..89643ff82da 100644 --- a/public/app/features/variables/query/reducer.test.ts +++ b/public/app/features/variables/query/reducer.test.ts @@ -1,18 +1,20 @@ import { cloneDeep } from 'lodash'; + import { MetricFindValue } from '@grafana/data'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { getVariableTestContext } from '../state/helpers'; +import { VariablesState } from '../state/types'; +import { QueryVariableModel, VariableSort } from '../types'; +import { toVariablePayload } from '../utils'; + +import { createQueryVariableAdapter } from './adapter'; import { metricNamesToVariableValues, queryVariableReducer, sortVariableValues, updateVariableOptions, } from './reducer'; -import { QueryVariableModel, VariableSort } from '../types'; -import { getVariableTestContext } from '../state/helpers'; -import { VariablesState } from '../state/types'; -import { createQueryVariableAdapter } from './adapter'; -import { toVariablePayload } from '../utils'; describe('queryVariableReducer', () => { const adapter = createQueryVariableAdapter(); diff --git a/public/app/features/variables/query/reducer.ts b/public/app/features/variables/query/reducer.ts index e4afc5c609d..91b895bf8c4 100644 --- a/public/app/features/variables/query/reducer.ts +++ b/public/app/features/variables/query/reducer.ts @@ -1,12 +1,12 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { isNumber, sortBy, toLower, uniqBy } from 'lodash'; + import { MetricFindValue, stringToJsRegex } from '@grafana/data'; -import { initialVariableModelState, QueryVariableModel, VariableOption, VariableRefresh, VariableSort } from '../types'; - -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, NONE_VARIABLE_TEXT, NONE_VARIABLE_VALUE } from '../constants'; import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; +import { initialVariableModelState, QueryVariableModel, VariableOption, VariableRefresh, VariableSort } from '../types'; interface VariableOptionsUpdate { templatedRegex: string; diff --git a/public/app/features/variables/query/variableQueryObserver.test.ts b/public/app/features/variables/query/variableQueryObserver.test.ts index 8232e74df55..3730b39c725 100644 --- a/public/app/features/variables/query/variableQueryObserver.test.ts +++ b/public/app/features/variables/query/variableQueryObserver.test.ts @@ -1,7 +1,9 @@ -import { variableQueryObserver } from './variableQueryObserver'; import { LoadingState } from '@grafana/data'; + import { KeyedVariableIdentifier } from '../state/types'; + import { UpdateOptionsResults } from './VariableQueryRunner'; +import { variableQueryObserver } from './variableQueryObserver'; function getTestContext(args: { next?: UpdateOptionsResults; error?: any; complete?: boolean }) { const { next, error, complete } = args; diff --git a/public/app/features/variables/query/variableQueryObserver.ts b/public/app/features/variables/query/variableQueryObserver.ts index 9ad39a14f26..f6cddbb3059 100644 --- a/public/app/features/variables/query/variableQueryObserver.ts +++ b/public/app/features/variables/query/variableQueryObserver.ts @@ -1,4 +1,5 @@ import { Observer, Subscription } from 'rxjs'; + import { LoadingState } from '@grafana/data'; import { UpdateOptionsResults } from './VariableQueryRunner'; diff --git a/public/app/features/variables/shared/formatVariable.ts b/public/app/features/variables/shared/formatVariable.ts index 3cb47e741b2..34d798c68fa 100644 --- a/public/app/features/variables/shared/formatVariable.ts +++ b/public/app/features/variables/shared/formatVariable.ts @@ -1,4 +1,5 @@ import { VariableModel } from '@grafana/data'; + import { VariableWithOptions } from '../types'; export const formatVariableLabel = (variable: VariableModel) => { diff --git a/public/app/features/variables/shared/multiOptions.test.ts b/public/app/features/variables/shared/multiOptions.test.ts index 51d423067fe..f660088de10 100644 --- a/public/app/features/variables/shared/multiOptions.test.ts +++ b/public/app/features/variables/shared/multiOptions.test.ts @@ -1,4 +1,5 @@ import { VariableOption } from 'app/features/variables/types'; + import { alignCurrentWithMulti } from './multiOptions'; describe('alignCurrentWithMulti', () => { diff --git a/public/app/features/variables/shared/testing/adHocVariableBuilder.ts b/public/app/features/variables/shared/testing/adHocVariableBuilder.ts index 973e0fd62f8..724e3c19a60 100644 --- a/public/app/features/variables/shared/testing/adHocVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/adHocVariableBuilder.ts @@ -1,5 +1,6 @@ import { DataSourceRef } from '@grafana/data'; import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types'; + import { VariableBuilder } from './variableBuilder'; export class AdHocVariableBuilder extends VariableBuilder { diff --git a/public/app/features/variables/shared/testing/builders.ts b/public/app/features/variables/shared/testing/builders.ts index c2351663973..ed072e8ea53 100644 --- a/public/app/features/variables/shared/testing/builders.ts +++ b/public/app/features/variables/shared/testing/builders.ts @@ -1,15 +1,16 @@ -import { AdHocVariableBuilder } from './adHocVariableBuilder'; -import { IntervalVariableBuilder } from './intervalVariableBuilder'; -import { DatasourceVariableBuilder } from './datasourceVariableBuilder'; -import { OptionsVariableBuilder } from './optionsVariableBuilder'; -import { initialQueryVariableModelState } from '../../query/reducer'; import { initialAdHocVariableModelState } from '../../adhoc/reducer'; +import { initialConstantVariableModelState } from '../../constant/reducer'; +import { initialCustomVariableModelState } from '../../custom/reducer'; import { initialDataSourceVariableModelState } from '../../datasource/reducer'; import { initialIntervalVariableModelState } from '../../interval/reducer'; +import { initialQueryVariableModelState } from '../../query/reducer'; import { initialTextBoxVariableModelState } from '../../textbox/reducer'; -import { initialCustomVariableModelState } from '../../custom/reducer'; + +import { AdHocVariableBuilder } from './adHocVariableBuilder'; +import { DatasourceVariableBuilder } from './datasourceVariableBuilder'; +import { IntervalVariableBuilder } from './intervalVariableBuilder'; import { MultiVariableBuilder } from './multiVariableBuilder'; -import { initialConstantVariableModelState } from '../../constant/reducer'; +import { OptionsVariableBuilder } from './optionsVariableBuilder'; import { QueryVariableBuilder } from './queryVariableBuilder'; import { TextBoxVariableBuilder } from './textboxVariableBuilder'; diff --git a/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts b/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts index 39e58ea7e63..db4c9be4ed6 100644 --- a/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/datasourceVariableBuilder.ts @@ -1,6 +1,7 @@ -import { MultiVariableBuilder } from './multiVariableBuilder'; import { DataSourceVariableModel, VariableRefresh } from 'app/features/variables/types'; +import { MultiVariableBuilder } from './multiVariableBuilder'; + export class DatasourceVariableBuilder extends MultiVariableBuilder { withRefresh(refresh: VariableRefresh) { this.variable.refresh = refresh; diff --git a/public/app/features/variables/shared/testing/intervalVariableBuilder.ts b/public/app/features/variables/shared/testing/intervalVariableBuilder.ts index fa6358acaa4..3979d194932 100644 --- a/public/app/features/variables/shared/testing/intervalVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/intervalVariableBuilder.ts @@ -1,6 +1,7 @@ -import { OptionsVariableBuilder } from './optionsVariableBuilder'; import { IntervalVariableModel, VariableRefresh } from 'app/features/variables/types'; +import { OptionsVariableBuilder } from './optionsVariableBuilder'; + export class IntervalVariableBuilder extends OptionsVariableBuilder { withRefresh(refresh: VariableRefresh) { this.variable.refresh = refresh; diff --git a/public/app/features/variables/shared/testing/multiVariableBuilder.ts b/public/app/features/variables/shared/testing/multiVariableBuilder.ts index f4da7108051..3fb6309f381 100644 --- a/public/app/features/variables/shared/testing/multiVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/multiVariableBuilder.ts @@ -1,4 +1,5 @@ import { VariableWithMultiSupport } from 'app/features/variables/types'; + import { OptionsVariableBuilder } from './optionsVariableBuilder'; export class MultiVariableBuilder extends OptionsVariableBuilder { diff --git a/public/app/features/variables/shared/testing/optionsVariableBuilder.ts b/public/app/features/variables/shared/testing/optionsVariableBuilder.ts index 271defad78e..18f2100e0b1 100644 --- a/public/app/features/variables/shared/testing/optionsVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/optionsVariableBuilder.ts @@ -1,4 +1,5 @@ import { VariableOption, VariableWithOptions } from 'app/features/variables/types'; + import { VariableBuilder } from './variableBuilder'; export class OptionsVariableBuilder extends VariableBuilder { diff --git a/public/app/features/variables/shared/testing/queryVariableBuilder.ts b/public/app/features/variables/shared/testing/queryVariableBuilder.ts index fbfd4843f10..d7455324c97 100644 --- a/public/app/features/variables/shared/testing/queryVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/queryVariableBuilder.ts @@ -1,6 +1,6 @@ import { DataSourceRef } from '@grafana/data'; - import { QueryVariableModel } from 'app/features/variables/types'; + import { DatasourceVariableBuilder } from './datasourceVariableBuilder'; export class QueryVariableBuilder extends DatasourceVariableBuilder { diff --git a/public/app/features/variables/shared/testing/textboxVariableBuilder.ts b/public/app/features/variables/shared/testing/textboxVariableBuilder.ts index 6170c7ee8f7..497e8741917 100644 --- a/public/app/features/variables/shared/testing/textboxVariableBuilder.ts +++ b/public/app/features/variables/shared/testing/textboxVariableBuilder.ts @@ -1,4 +1,5 @@ import { TextBoxVariableModel } from 'app/features/variables/types'; + import { OptionsVariableBuilder } from './optionsVariableBuilder'; export class TextBoxVariableBuilder extends OptionsVariableBuilder { diff --git a/public/app/features/variables/shared/testing/variableBuilder.ts b/public/app/features/variables/shared/testing/variableBuilder.ts index f966605787b..1b8cdaccfce 100644 --- a/public/app/features/variables/shared/testing/variableBuilder.ts +++ b/public/app/features/variables/shared/testing/variableBuilder.ts @@ -1,4 +1,5 @@ import { cloneDeep } from 'lodash'; + import { VariableModel } from 'app/features/variables/types'; export class VariableBuilder { diff --git a/public/app/features/variables/state/actions.test.ts b/public/app/features/variables/state/actions.test.ts index e73555705bf..89f5579348f 100644 --- a/public/app/features/variables/state/actions.test.ts +++ b/public/app/features/variables/state/actions.test.ts @@ -1,12 +1,36 @@ import { AnyAction } from 'redux'; -import { getPreloadedState, getTemplatingRootReducer, TemplatingReducerType } from './helpers'; -import { variableAdapters } from '../adapters'; -import { createQueryVariableAdapter } from '../query/adapter'; -import { createCustomVariableAdapter } from '../custom/adapter'; -import { createTextBoxVariableAdapter } from '../textbox/adapter'; -import { createConstantVariableAdapter } from '../constant/adapter'; +import { LoadingState } from '@grafana/data'; +import * as runtime from '@grafana/runtime'; + import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { toAsyncOfResult } from '../../query/state/DashboardQueryRunner/testHelpers'; +import { variableAdapters } from '../adapters'; +import { createConstantVariableAdapter } from '../constant/adapter'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, NEW_VARIABLE_ID } from '../constants'; +import { createCustomVariableAdapter } from '../custom/adapter'; +import { changeVariableName } from '../editor/actions'; +import { + changeVariableNameFailed, + changeVariableNameSucceeded, + cleanEditorState, + setIdInEditor, +} from '../editor/reducer'; +import { cleanPickerState } from '../pickers/OptionsPicker/reducer'; +import { setVariableQueryRunner, VariableQueryRunner } from '../query/VariableQueryRunner'; +import { createQueryVariableAdapter } from '../query/adapter'; +import { updateVariableOptions } from '../query/reducer'; +import { + constantBuilder, + customBuilder, + datasourceBuilder, + queryBuilder, + textboxBuilder, +} from '../shared/testing/builders'; +import { createTextBoxVariableAdapter } from '../textbox/adapter'; +import { ConstantVariableModel, VariableRefresh } from '../types'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + import { cancelVariables, changeVariableMultiValue, @@ -17,6 +41,8 @@ import { processVariables, validateVariableSelectionState, } from './actions'; +import { getPreloadedState, getTemplatingRootReducer, TemplatingReducerType } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; import { addVariable, changeVariableProp, @@ -26,32 +52,8 @@ import { variableStateFetching, variableStateNotStarted, } from './sharedReducer'; -import { - constantBuilder, - customBuilder, - datasourceBuilder, - queryBuilder, - textboxBuilder, -} from '../shared/testing/builders'; -import { changeVariableName } from '../editor/actions'; -import { - changeVariableNameFailed, - changeVariableNameSucceeded, - cleanEditorState, - setIdInEditor, -} from '../editor/reducer'; import { variablesClearTransaction, variablesInitTransaction } from './transactionReducer'; -import { cleanPickerState } from '../pickers/OptionsPicker/reducer'; import { cleanVariables } from './variablesReducer'; -import { ConstantVariableModel, VariableRefresh } from '../types'; -import { updateVariableOptions } from '../query/reducer'; -import { setVariableQueryRunner, VariableQueryRunner } from '../query/VariableQueryRunner'; -import * as runtime from '@grafana/runtime'; -import { LoadingState } from '@grafana/data'; -import { toAsyncOfResult } from '../../query/state/DashboardQueryRunner/testHelpers'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE, NEW_VARIABLE_ID } from '../constants'; -import { toKeyedAction } from './keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; variableAdapters.setInit(() => [ createQueryVariableAdapter(), diff --git a/public/app/features/variables/state/actions.ts b/public/app/features/variables/state/actions.ts index 6c064431173..5a31fc12e89 100644 --- a/public/app/features/variables/state/actions.ts +++ b/public/app/features/variables/state/actions.ts @@ -1,4 +1,5 @@ import { castArray, isEqual } from 'lodash'; + import { DataQuery, getDataSourceRef, @@ -8,7 +9,36 @@ import { UrlQueryMap, UrlQueryValue, } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { notifyApp } from 'app/core/actions'; +import { contextSrv } from 'app/core/services/context_srv'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { DashboardModel } from 'app/features/dashboard/state'; +import { store } from 'app/store/store'; +import { createErrorNotification } from '../../../core/copy/appNotification'; +import { appEvents } from '../../../core/core'; +import { getBackendSrv } from '../../../core/services/backend_srv'; +import { Graph } from '../../../core/utils/dag'; +import { AppNotification, StoreState, ThunkResult } from '../../../types'; +import { getDatasourceSrv } from '../../plugins/datasource_srv'; +import { getTemplateSrv, TemplateSrv } from '../../templating/template_srv'; +import { variableAdapters } from '../adapters'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; +import { cleanEditorState } from '../editor/reducer'; +import { + hasCurrent, + hasLegacyVariableSupport, + hasOptions, + hasStandardVariableSupport, + isAdHoc, + isConstant, + isMulti, + isQuery, +} from '../guard'; +import { getAllAffectedPanelIdsForVariableChange } from '../inspect/utils'; +import { cleanPickerState } from '../pickers/OptionsPicker/reducer'; +import { alignCurrentWithMulti } from '../shared/multiOptions'; import { DashboardVariableModel, initialVariableModelState, @@ -27,44 +57,6 @@ import { VariableWithMultiSupport, VariableWithOptions, } from '../types'; -import { AppNotification, StoreState, ThunkResult } from '../../../types'; -import { getIfExistsLastKey, getVariable, getVariablesByKey, getVariablesState } from './selectors'; -import { variableAdapters } from '../adapters'; -import { Graph } from '../../../core/utils/dag'; -import { notifyApp } from 'app/core/actions'; -import { - addVariable, - changeVariableProp, - setCurrentVariableValue, - variableStateCompleted, - variableStateFailed, - variableStateFetching, - variableStateNotStarted, -} from './sharedReducer'; -import { KeyedVariableIdentifier } from './types'; -import { contextSrv } from 'app/core/services/context_srv'; -import { getTemplateSrv, TemplateSrv } from '../../templating/template_srv'; -import { alignCurrentWithMulti } from '../shared/multiOptions'; -import { - hasCurrent, - hasLegacyVariableSupport, - hasOptions, - hasStandardVariableSupport, - isAdHoc, - isConstant, - isMulti, - isQuery, -} from '../guard'; -import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { DashboardModel } from 'app/features/dashboard/state'; -import { createErrorNotification } from '../../../core/copy/appNotification'; -import { - variablesClearTransaction, - variablesCompleteTransaction, - variablesInitTransaction, -} from './transactionReducer'; -import { getBackendSrv } from '../../../core/services/backend_srv'; -import { cleanVariables } from './variablesReducer'; import { ensureStringValues, ExtendedUrlQueryMap, @@ -75,15 +67,25 @@ import { toStateKey, toVariablePayload, } from '../utils'; -import { store } from 'app/store/store'; -import { getDatasourceSrv } from '../../plugins/datasource_srv'; -import { cleanEditorState } from '../editor/reducer'; -import { cleanPickerState } from '../pickers/OptionsPicker/reducer'; -import { locationService } from '@grafana/runtime'; -import { appEvents } from '../../../core/core'; -import { getAllAffectedPanelIdsForVariableChange } from '../inspect/utils'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; + import { toKeyedAction } from './keyedVariablesReducer'; +import { getIfExistsLastKey, getVariable, getVariablesByKey, getVariablesState } from './selectors'; +import { + addVariable, + changeVariableProp, + setCurrentVariableValue, + variableStateCompleted, + variableStateFailed, + variableStateFetching, + variableStateNotStarted, +} from './sharedReducer'; +import { + variablesClearTransaction, + variablesCompleteTransaction, + variablesInitTransaction, +} from './transactionReducer'; +import { KeyedVariableIdentifier } from './types'; +import { cleanVariables } from './variablesReducer'; // process flow queryVariable // thunk => processVariables diff --git a/public/app/features/variables/state/helpers.ts b/public/app/features/variables/state/helpers.ts index 5ea13a64fd9..70db7abdeb3 100644 --- a/public/app/features/variables/state/helpers.ts +++ b/public/app/features/variables/state/helpers.ts @@ -1,7 +1,11 @@ import { combineReducers } from '@reduxjs/toolkit'; -import { LoadingState } from '@grafana/data'; -import { VariablesState } from './types'; +import { LoadingState } from '@grafana/data'; +import { dashboardReducer } from 'app/features/dashboard/state/reducers'; + +import { DashboardState, StoreState } from '../../../types'; +import { VariableAdapter } from '../adapters'; +import { NEW_VARIABLE_ID } from '../constants'; import { DashboardVariableModel, initialVariableModelState, @@ -11,12 +15,9 @@ import { VariableModel, } from '../types'; -import { VariableAdapter } from '../adapters'; -import { dashboardReducer } from 'app/features/dashboard/state/reducers'; -import { DashboardState, StoreState } from '../../../types'; -import { NEW_VARIABLE_ID } from '../constants'; import { keyedVariablesReducer, KeyedVariablesState } from './keyedVariablesReducer'; import { getInitialTemplatingState, TemplatingState } from './reducers'; +import { VariablesState } from './types'; export const getVariableState = ( noOfVariables: number, diff --git a/public/app/features/variables/state/initVariableTransaction.test.ts b/public/app/features/variables/state/initVariableTransaction.test.ts index 592d46cc68a..c8830f3674e 100644 --- a/public/app/features/variables/state/initVariableTransaction.test.ts +++ b/public/app/features/variables/state/initVariableTransaction.test.ts @@ -1,8 +1,24 @@ -import { getPreloadedState, getRootReducer, RootReducerType } from './helpers'; -import { variableAdapters } from '../adapters'; -import { createQueryVariableAdapter } from '../query/adapter'; -import { createConstantVariableAdapter } from '../constant/adapter'; +import { DataSourceRef, LoadingState } from '@grafana/data/src'; +import { setDataSourceSrv } from '@grafana/runtime/src'; + import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { toAsyncOfResult } from '../../query/state/DashboardQueryRunner/testHelpers'; +import { variableAdapters } from '../adapters'; +import { createAdHocVariableAdapter } from '../adhoc/adapter'; +import { createConstantVariableAdapter } from '../constant/adapter'; +import { createDataSourceVariableAdapter } from '../datasource/adapter'; +import { createDataSourceOptions } from '../datasource/reducer'; +import { cleanEditorState } from '../editor/reducer'; +import { cleanPickerState } from '../pickers/OptionsPicker/reducer'; +import { setVariableQueryRunner } from '../query/VariableQueryRunner'; +import { createQueryVariableAdapter } from '../query/adapter'; +import { adHocBuilder, constantBuilder, datasourceBuilder, queryBuilder } from '../shared/testing/builders'; +import { TransactionStatus, VariableModel } from '../types'; +import { toVariablePayload } from '../utils'; + +import { initVariablesTransaction } from './actions'; +import { getPreloadedState, getRootReducer, RootReducerType } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; import { addVariable, changeVariableProp, @@ -11,27 +27,13 @@ import { variableStateFetching, variableStateNotStarted, } from './sharedReducer'; -import { adHocBuilder, constantBuilder, datasourceBuilder, queryBuilder } from '../shared/testing/builders'; -import { cleanEditorState } from '../editor/reducer'; import { initialTransactionState, variablesClearTransaction, variablesCompleteTransaction, variablesInitTransaction, } from './transactionReducer'; -import { cleanPickerState } from '../pickers/OptionsPicker/reducer'; import { cleanVariables } from './variablesReducer'; -import { createAdHocVariableAdapter } from '../adhoc/adapter'; -import { createDataSourceVariableAdapter } from '../datasource/adapter'; -import { DataSourceRef, LoadingState } from '@grafana/data/src'; -import { setDataSourceSrv } from '@grafana/runtime/src'; -import { TransactionStatus, VariableModel } from '../types'; -import { toAsyncOfResult } from '../../query/state/DashboardQueryRunner/testHelpers'; -import { setVariableQueryRunner } from '../query/VariableQueryRunner'; -import { createDataSourceOptions } from '../datasource/reducer'; -import { initVariablesTransaction } from './actions'; -import { toKeyedAction } from './keyedVariablesReducer'; -import { toVariablePayload } from '../utils'; variableAdapters.setInit(() => [ createQueryVariableAdapter(), diff --git a/public/app/features/variables/state/keyedVariablesReducer.test.ts b/public/app/features/variables/state/keyedVariablesReducer.test.ts index 4a4dfa346f5..9d6e99a2168 100644 --- a/public/app/features/variables/state/keyedVariablesReducer.test.ts +++ b/public/app/features/variables/state/keyedVariablesReducer.test.ts @@ -1,13 +1,14 @@ +import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { TransactionStatus } from '../types'; + import { initialKeyedVariablesState, keyedVariablesReducer, KeyedVariablesState, toKeyedAction, } from './keyedVariablesReducer'; -import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { initialTransactionState, variablesCompleteTransaction, variablesInitTransaction } from './transactionReducer'; -import { TransactionStatus } from '../types'; import { getInitialTemplatingState } from './reducers'; +import { initialTransactionState, variablesCompleteTransaction, variablesInitTransaction } from './transactionReducer'; describe('dashboardVariablesReducer', () => { describe('when an toUidAction is dispatched', () => { diff --git a/public/app/features/variables/state/keyedVariablesReducer.ts b/public/app/features/variables/state/keyedVariablesReducer.ts index c3fd48ed5c6..54f09d50d8d 100644 --- a/public/app/features/variables/state/keyedVariablesReducer.ts +++ b/public/app/features/variables/state/keyedVariablesReducer.ts @@ -1,8 +1,10 @@ +import { PayloadAction } from '@reduxjs/toolkit'; import { AnyAction } from 'redux'; -import { createAction, PayloadAction } from '@reduxjs/toolkit'; + +import { toStateKey } from '../utils'; + import { getTemplatingReducers, TemplatingState } from './reducers'; import { variablesInitTransaction } from './transactionReducer'; -import { toStateKey } from '../utils'; export interface KeyedVariablesState { lastKey?: string; @@ -16,15 +18,27 @@ export interface KeyedAction { action: PayloadAction; } -const keyedAction = createAction('templating/keyedAction'); +const keyedAction = (payload: KeyedAction) => ({ + type: `templating/keyed/${payload.action.type.replace(/^templating\//, '')}`, + payload, +}); export function toKeyedAction(key: string, action: PayloadAction): PayloadAction { const keyAsString = toStateKey(key); return keyedAction({ key: keyAsString, action }); } +const isKeyedAction = (action: AnyAction): action is PayloadAction => { + return ( + typeof action.type === 'string' && + action.type.startsWith('templating/keyed') && + 'payload' in action && + typeof action.payload.key === 'string' + ); +}; + export function keyedVariablesReducer(state = initialKeyedVariablesState, outerAction: AnyAction): KeyedVariablesState { - if (keyedAction.match(outerAction)) { + if (isKeyedAction(outerAction)) { const { key, action } = outerAction.payload; const stringKey = toStateKey(key); const lastKey = variablesInitTransaction.match(action) ? stringKey : state.lastKey; diff --git a/public/app/features/variables/state/migrateVariablesDatasourceNameToRef.test.ts b/public/app/features/variables/state/migrateVariablesDatasourceNameToRef.test.ts index 65cf7a33559..aea1a31a10b 100644 --- a/public/app/features/variables/state/migrateVariablesDatasourceNameToRef.test.ts +++ b/public/app/features/variables/state/migrateVariablesDatasourceNameToRef.test.ts @@ -1,11 +1,13 @@ -import { migrateVariablesDatasourceNameToRef } from './actions'; -import { adHocBuilder, queryBuilder } from '../shared/testing/builders'; import { DataSourceRef } from '@grafana/data/src'; -import { changeVariableProp } from './sharedReducer'; -import { toKeyedAction } from './keyedVariablesReducer'; -import { getPreloadedState } from './helpers'; + +import { adHocBuilder, queryBuilder } from '../shared/testing/builders'; import { toVariablePayload } from '../utils'; +import { migrateVariablesDatasourceNameToRef } from './actions'; +import { getPreloadedState } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; +import { changeVariableProp } from './sharedReducer'; + function getTestContext(ds: DataSourceRef, dsInstance?: { uid: string; type: string }) { jest.clearAllMocks(); const key = 'key'; diff --git a/public/app/features/variables/state/onTimeRangeUpdated.test.ts b/public/app/features/variables/state/onTimeRangeUpdated.test.ts index 388916def33..774467c69b0 100644 --- a/public/app/features/variables/state/onTimeRangeUpdated.test.ts +++ b/public/app/features/variables/state/onTimeRangeUpdated.test.ts @@ -1,30 +1,31 @@ import { dateTime, TimeRange } from '@grafana/data'; -import { TemplateSrv } from '../../templating/template_srv'; -import { onTimeRangeUpdated, OnTimeRangeUpdatedDependencies, setOptionAsCurrent } from './actions'; -import { DashboardModel } from '../../dashboard/state'; +import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import { expect } from '../../../../test/lib/common'; +import { appEvents } from '../../../core/core'; +import { notifyApp } from '../../../core/reducers/appNotification'; import { DashboardState } from '../../../types'; -import { createIntervalVariableAdapter } from '../interval/adapter'; +import { DashboardModel } from '../../dashboard/state'; +import { TemplateSrv } from '../../templating/template_srv'; import { variableAdapters } from '../adapters'; import { createConstantVariableAdapter } from '../constant/adapter'; -import { VariableRefresh } from '../types'; +import { createIntervalVariableAdapter } from '../interval/adapter'; +import { createIntervalOptions } from '../interval/reducer'; import { constantBuilder, intervalBuilder } from '../shared/testing/builders'; -import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { VariableRefresh } from '../types'; +import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + +import { onTimeRangeUpdated, OnTimeRangeUpdatedDependencies, setOptionAsCurrent } from './actions'; import { getPreloadedState, getRootReducer, RootReducerType } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; import { setCurrentVariableValue, variableStateCompleted, variableStateFailed, variableStateFetching, } from './sharedReducer'; -import { createIntervalOptions } from '../interval/reducer'; -import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; -import { notifyApp } from '../../../core/reducers/appNotification'; -import { expect } from '../../../../test/lib/common'; -import { appEvents } from '../../../core/core'; import { variablesInitTransaction } from './transactionReducer'; -import { toKeyedAction } from './keyedVariablesReducer'; -import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; variableAdapters.setInit(() => [createIntervalVariableAdapter(), createConstantVariableAdapter()]); diff --git a/public/app/features/variables/state/processVariable.test.ts b/public/app/features/variables/state/processVariable.test.ts index d05cfe193fe..391461adab2 100644 --- a/public/app/features/variables/state/processVariable.test.ts +++ b/public/app/features/variables/state/processVariable.test.ts @@ -1,21 +1,22 @@ import { UrlQueryMap } from '@grafana/data'; +import { setDataSourceSrv } from '@grafana/runtime'; -import { getTemplatingRootReducer, TemplatingReducerType } from './helpers'; -import { variableAdapters } from '../adapters'; -import { createQueryVariableAdapter } from '../query/adapter'; -import { createCustomVariableAdapter } from '../custom/adapter'; import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { initDashboardTemplating, processVariable } from './actions'; -import { setCurrentVariableValue, variableStateCompleted, variableStateFetching } from './sharedReducer'; -import { VariableRefresh } from '../types'; +import { variableAdapters } from '../adapters'; +import { createCustomVariableAdapter } from '../custom/adapter'; +import { setVariableQueryRunner, VariableQueryRunner } from '../query/VariableQueryRunner'; +import { createQueryVariableAdapter } from '../query/adapter'; import { updateVariableOptions } from '../query/reducer'; import { customBuilder, queryBuilder } from '../shared/testing/builders'; -import { variablesInitTransaction } from './transactionReducer'; -import { setVariableQueryRunner, VariableQueryRunner } from '../query/VariableQueryRunner'; -import { setDataSourceSrv } from '@grafana/runtime'; -import { toKeyedAction } from './keyedVariablesReducer'; +import { VariableRefresh } from '../types'; import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; +import { initDashboardTemplating, processVariable } from './actions'; +import { getTemplatingRootReducer, TemplatingReducerType } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; +import { setCurrentVariableValue, variableStateCompleted, variableStateFetching } from './sharedReducer'; +import { variablesInitTransaction } from './transactionReducer'; + jest.mock('app/features/dashboard/services/TimeSrv', () => ({ getTimeSrv: jest.fn().mockReturnValue({ timeRange: jest.fn().mockReturnValue({ diff --git a/public/app/features/variables/state/reducers.test.ts b/public/app/features/variables/state/reducers.test.ts index 3e156e2236a..fdf61d8cbab 100644 --- a/public/app/features/variables/state/reducers.test.ts +++ b/public/app/features/variables/state/reducers.test.ts @@ -1,13 +1,15 @@ import { createAction } from '@reduxjs/toolkit'; + import { VariableType } from '@grafana/data'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; -import { initialVariableModelState, QueryVariableModel } from '../types'; import { VariableAdapter, variableAdapters } from '../adapters'; -import { cleanVariables, variablesReducer } from './variablesReducer'; -import { VariablePayload, VariablesState } from './types'; +import { initialVariableModelState, QueryVariableModel } from '../types'; import { toVariablePayload } from '../utils'; +import { VariablePayload, VariablesState } from './types'; +import { cleanVariables, variablesReducer } from './variablesReducer'; + const variableAdapter: VariableAdapter = { id: 'mock' as unknown as VariableType, name: 'Mock label', diff --git a/public/app/features/variables/state/reducers.ts b/public/app/features/variables/state/reducers.ts index 7cc63679118..64eb75f45a2 100644 --- a/public/app/features/variables/state/reducers.ts +++ b/public/app/features/variables/state/reducers.ts @@ -1,10 +1,12 @@ import { CombinedState, combineReducers, Reducer } from 'redux'; -import { initialOptionPickerState, optionsPickerReducer, OptionsPickerState } from '../pickers/OptionsPicker/reducer'; + import { initialVariableEditorState, variableEditorReducer, VariableEditorState } from '../editor/reducer'; -import { variablesReducer } from './variablesReducer'; -import { initialTransactionState, transactionReducer, TransactionState } from './transactionReducer'; import { initialVariableInspectState, variableInspectReducer, VariableInspectState } from '../inspect/reducer'; +import { initialOptionPickerState, optionsPickerReducer, OptionsPickerState } from '../pickers/OptionsPicker/reducer'; + +import { initialTransactionState, transactionReducer, TransactionState } from './transactionReducer'; import { initialVariablesState, VariablesState } from './types'; +import { variablesReducer } from './variablesReducer'; export interface TemplatingState { editor: VariableEditorState; diff --git a/public/app/features/variables/state/selectors.ts b/public/app/features/variables/state/selectors.ts index 685b0b9b51d..301b2341ea4 100644 --- a/public/app/features/variables/state/selectors.ts +++ b/public/app/features/variables/state/selectors.ts @@ -1,9 +1,11 @@ +import memoizeOne from 'memoize-one'; + +import { getState } from '../../../store/store'; import { StoreState } from '../../../types'; import { VariableModel } from '../types'; -import { getState } from '../../../store/store'; -import memoizeOne from 'memoize-one'; -import { getInitialTemplatingState, TemplatingState } from './reducers'; import { toStateKey } from '../utils'; + +import { getInitialTemplatingState, TemplatingState } from './reducers'; import { KeyedVariableIdentifier, VariablesState } from './types'; export const getVariable = ( diff --git a/public/app/features/variables/state/setOptionFromUrl.test.ts b/public/app/features/variables/state/setOptionFromUrl.test.ts index 5128deefe8a..8e5b5476e1f 100644 --- a/public/app/features/variables/state/setOptionFromUrl.test.ts +++ b/public/app/features/variables/state/setOptionFromUrl.test.ts @@ -1,14 +1,15 @@ +import { reduxTester } from '../../../../test/core/redux/reduxTester'; import { variableAdapters } from '../adapters'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; import { createCustomVariableAdapter } from '../custom/adapter'; import { customBuilder } from '../shared/testing/builders'; -import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { getTemplatingRootReducer, TemplatingReducerType } from './helpers'; -import { addVariable, setCurrentVariableValue } from './sharedReducer'; -import { setOptionFromUrl } from './actions'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; -import { toKeyedAction } from './keyedVariablesReducer'; import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; +import { setOptionFromUrl } from './actions'; +import { getTemplatingRootReducer, TemplatingReducerType } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; +import { addVariable, setCurrentVariableValue } from './sharedReducer'; + variableAdapters.setInit(() => [createCustomVariableAdapter()]); describe('when setOptionFromUrl is dispatched with a custom variable (no refresh property)', () => { diff --git a/public/app/features/variables/state/sharedReducer.test.ts b/public/app/features/variables/state/sharedReducer.test.ts index ec1b2deb01b..5e6e10d2811 100644 --- a/public/app/features/variables/state/sharedReducer.test.ts +++ b/public/app/features/variables/state/sharedReducer.test.ts @@ -1,7 +1,19 @@ import { cloneDeep } from 'lodash'; + import { LoadingState, VariableType } from '@grafana/data'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { variableAdapters } from '../adapters'; +import { createConstantVariableAdapter } from '../constant/adapter'; +import { initialConstantVariableModelState } from '../constant/reducer'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; +import { changeVariableNameSucceeded } from '../editor/reducer'; +import { createQueryVariableAdapter } from '../query/adapter'; +import { initialQueryVariableModelState } from '../query/reducer'; +import { ConstantVariableModel, QueryVariableModel, VariableHide, VariableOption } from '../types'; +import { toVariablePayload } from '../utils'; + +import { getVariableState, getVariableTestContext } from './helpers'; import { addVariable, changeVariableOrder, @@ -16,17 +28,7 @@ import { variableStateFetching, variableStateNotStarted, } from './sharedReducer'; -import { ConstantVariableModel, QueryVariableModel, VariableHide, VariableOption } from '../types'; import { initialVariablesState, KeyedVariableIdentifier, VariablesState } from './types'; -import { variableAdapters } from '../adapters'; -import { createQueryVariableAdapter } from '../query/adapter'; -import { initialQueryVariableModelState } from '../query/reducer'; -import { getVariableState, getVariableTestContext } from './helpers'; -import { changeVariableNameSucceeded } from '../editor/reducer'; -import { createConstantVariableAdapter } from '../constant/adapter'; -import { initialConstantVariableModelState } from '../constant/reducer'; -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../constants'; -import { toVariablePayload } from '../utils'; variableAdapters.setInit(() => [createQueryVariableAdapter(), createConstantVariableAdapter()]); diff --git a/public/app/features/variables/state/sharedReducer.ts b/public/app/features/variables/state/sharedReducer.ts index 7b5e4ecb6ce..53c29487412 100644 --- a/public/app/features/variables/state/sharedReducer.ts +++ b/public/app/features/variables/state/sharedReducer.ts @@ -1,12 +1,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { cloneDeep, defaults as lodashDefaults } from 'lodash'; + import { LoadingState, VariableType } from '@grafana/data'; -import { VariableModel, VariableOption, VariableWithOptions } from '../types'; -import { AddVariable, initialVariablesState, VariablePayload, VariablesState } from './types'; + import { variableAdapters } from '../adapters'; import { changeVariableNameSucceeded } from '../editor/reducer'; +import { VariableModel, VariableOption, VariableWithOptions } from '../types'; import { ensureStringValues } from '../utils'; + import { getInstanceState, getNextVariableIndex } from './selectors'; +import { AddVariable, initialVariablesState, VariablePayload, VariablesState } from './types'; const sharedReducerSlice = createSlice({ name: 'templating/shared', diff --git a/public/app/features/variables/state/templateVarsChangedInUrl.test.ts b/public/app/features/variables/state/templateVarsChangedInUrl.test.ts index 7163f2c9bf0..d50ad5d8dcd 100644 --- a/public/app/features/variables/state/templateVarsChangedInUrl.test.ts +++ b/public/app/features/variables/state/templateVarsChangedInUrl.test.ts @@ -1,15 +1,16 @@ -import { variableAdapters } from '../adapters'; -import { constantBuilder, customBuilder } from '../shared/testing/builders'; import { DashboardState, StoreState } from '../../../types'; -import { initialState } from '../../dashboard/state/reducers'; -import { ExtendedUrlQueryMap } from '../utils'; -import { templateVarsChangedInUrl } from './actions'; -import { createCustomVariableAdapter } from '../custom/adapter'; -import { VariablesState } from './types'; import { DashboardModel } from '../../dashboard/state'; -import { getPreloadedState } from './helpers'; +import { initialState } from '../../dashboard/state/reducers'; +import { variableAdapters } from '../adapters'; import { createConstantVariableAdapter } from '../constant/adapter'; +import { createCustomVariableAdapter } from '../custom/adapter'; +import { constantBuilder, customBuilder } from '../shared/testing/builders'; import { VariableModel } from '../types'; +import { ExtendedUrlQueryMap } from '../utils'; + +import { templateVarsChangedInUrl } from './actions'; +import { getPreloadedState } from './helpers'; +import { VariablesState } from './types'; const dashboardModel = new DashboardModel({}); diff --git a/public/app/features/variables/state/transactionReducer.test.ts b/public/app/features/variables/state/transactionReducer.test.ts index 4772f55e48f..c769c10636a 100644 --- a/public/app/features/variables/state/transactionReducer.test.ts +++ b/public/app/features/variables/state/transactionReducer.test.ts @@ -1,4 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; +import { TransactionStatus } from '../types'; + import { removeVariable, variableStateNotStarted } from './sharedReducer'; import { initialTransactionState, @@ -8,7 +10,6 @@ import { variablesCompleteTransaction, variablesInitTransaction, } from './transactionReducer'; -import { TransactionStatus } from '../types'; describe('transactionReducer', () => { describe('when variablesInitTransaction is dispatched', () => { diff --git a/public/app/features/variables/state/transactionReducer.ts b/public/app/features/variables/state/transactionReducer.ts index 05d4a1c6cdf..0bce8898aba 100644 --- a/public/app/features/variables/state/transactionReducer.ts +++ b/public/app/features/variables/state/transactionReducer.ts @@ -1,4 +1,7 @@ import { AnyAction, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +import { TransactionStatus } from '../types'; + import { addVariable, changeVariableOrder, @@ -7,7 +10,6 @@ import { duplicateVariable, removeVariable, } from './sharedReducer'; -import { TransactionStatus } from '../types'; export interface TransactionState { uid: string | undefined | null; diff --git a/public/app/features/variables/state/upgradeLegacyQueries.test.ts b/public/app/features/variables/state/upgradeLegacyQueries.test.ts index d3f68d6e90d..bc4281ca926 100644 --- a/public/app/features/variables/state/upgradeLegacyQueries.test.ts +++ b/public/app/features/variables/state/upgradeLegacyQueries.test.ts @@ -1,13 +1,15 @@ -import { customBuilder, queryBuilder } from '../shared/testing/builders'; import { VariableSupportType } from '@grafana/data'; -import { upgradeLegacyQueries } from './actions'; -import { changeVariableProp } from './sharedReducer'; + import { thunkTester } from '../../../../test/core/thunk/thunkTester'; +import { customBuilder, queryBuilder } from '../shared/testing/builders'; import { TransactionStatus, VariableModel } from '../types'; -import { toKeyedAction } from './keyedVariablesReducer'; -import { getPreloadedState } from './helpers'; import { toKeyedVariableIdentifier } from '../utils'; +import { upgradeLegacyQueries } from './actions'; +import { getPreloadedState } from './helpers'; +import { toKeyedAction } from './keyedVariablesReducer'; +import { changeVariableProp } from './sharedReducer'; + interface Args { query?: any; variable?: VariableModel; diff --git a/public/app/features/variables/state/variablesReducer.ts b/public/app/features/variables/state/variablesReducer.ts index 3319196dd64..222fa558da1 100644 --- a/public/app/features/variables/state/variablesReducer.ts +++ b/public/app/features/variables/state/variablesReducer.ts @@ -1,8 +1,10 @@ import { createAction } from '@reduxjs/toolkit'; -import { variableAdapters } from '../adapters'; -import { sharedReducer } from './sharedReducer'; -import { VariableModel } from '../types'; import { AnyAction } from 'redux'; + +import { variableAdapters } from '../adapters'; +import { VariableModel } from '../types'; + +import { sharedReducer } from './sharedReducer'; import { initialVariablesState, VariablesState } from './types'; export const cleanVariables = createAction('templating/cleanVariables'); diff --git a/public/app/features/variables/system/adapter.ts b/public/app/features/variables/system/adapter.ts index 40f1e91d8e0..82b8c277da5 100644 --- a/public/app/features/variables/system/adapter.ts +++ b/public/app/features/variables/system/adapter.ts @@ -1,10 +1,11 @@ import { ComponentType } from 'react'; + import { LoadingState } from '@grafana/data'; -import { initialVariableModelState, SystemVariable, VariableHide } from '../types'; import { VariableAdapter } from '../adapters'; -import { VariablePickerProps } from '../pickers/types'; import { VariableEditorProps } from '../editor/types'; +import { VariablePickerProps } from '../pickers/types'; +import { initialVariableModelState, SystemVariable, VariableHide } from '../types'; export const createSystemVariableAdapter = (): VariableAdapter> => { return { diff --git a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx index cb13f732018..119291d63a9 100644 --- a/public/app/features/variables/textbox/TextBoxVariableEditor.tsx +++ b/public/app/features/variables/textbox/TextBoxVariableEditor.tsx @@ -1,11 +1,12 @@ import React, { FormEvent, ReactElement, useCallback } from 'react'; + +import { selectors } from '@grafana/e2e-selectors'; import { VerticalGroup } from '@grafana/ui'; -import { TextBoxVariableModel } from '../types'; -import { VariableEditorProps } from '../editor/types'; import { VariableSectionHeader } from '../editor/VariableSectionHeader'; import { VariableTextField } from '../editor/VariableTextField'; -import { selectors } from '@grafana/e2e-selectors'; +import { VariableEditorProps } from '../editor/types'; +import { TextBoxVariableModel } from '../types'; export interface Props extends VariableEditorProps {} diff --git a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx index 7ad4152683d..14d9251d9a7 100644 --- a/public/app/features/variables/textbox/TextBoxVariablePicker.tsx +++ b/public/app/features/variables/textbox/TextBoxVariablePicker.tsx @@ -1,12 +1,13 @@ import React, { ChangeEvent, FocusEvent, KeyboardEvent, ReactElement, useCallback, useEffect, useState } from 'react'; - -import { TextBoxVariableModel } from '../types'; -import { changeVariableProp } from '../state/sharedReducer'; -import { VariablePickerProps } from '../pickers/types'; -import { Input } from '@grafana/ui'; -import { variableAdapters } from '../adapters'; import { useDispatch } from 'react-redux'; + +import { Input } from '@grafana/ui'; + +import { variableAdapters } from '../adapters'; +import { VariablePickerProps } from '../pickers/types'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { changeVariableProp } from '../state/sharedReducer'; +import { TextBoxVariableModel } from '../types'; import { toVariablePayload } from '../utils'; export interface Props extends VariablePickerProps {} diff --git a/public/app/features/variables/textbox/actions.test.ts b/public/app/features/variables/textbox/actions.test.ts index 4c38b863523..117e41a5cb5 100644 --- a/public/app/features/variables/textbox/actions.test.ts +++ b/public/app/features/variables/textbox/actions.test.ts @@ -1,16 +1,18 @@ -import { variableAdapters } from '../adapters'; -import { createTextBoxVariableAdapter } from './adapter'; -import { reduxTester } from '../../../../test/core/redux/reduxTester'; -import { setTextBoxVariableOptionsFromUrl, updateTextBoxVariableOptions } from './actions'; -import { getRootReducer, RootReducerType } from '../state/helpers'; -import { VariableOption } from '../types'; -import { createTextBoxOptions } from './reducer'; -import { addVariable, changeVariableProp, setCurrentVariableValue } from '../state/sharedReducer'; -import { textboxBuilder } from '../shared/testing/builders'; import { locationService } from '@grafana/runtime'; + +import { reduxTester } from '../../../../test/core/redux/reduxTester'; +import { variableAdapters } from '../adapters'; +import { textboxBuilder } from '../shared/testing/builders'; +import { getRootReducer, RootReducerType } from '../state/helpers'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { addVariable, changeVariableProp, setCurrentVariableValue } from '../state/sharedReducer'; +import { VariableOption } from '../types'; import { toKeyedVariableIdentifier, toVariablePayload } from '../utils'; +import { setTextBoxVariableOptionsFromUrl, updateTextBoxVariableOptions } from './actions'; +import { createTextBoxVariableAdapter } from './adapter'; +import { createTextBoxOptions } from './reducer'; + jest.mock('@grafana/runtime', () => { const original = jest.requireActual('@grafana/runtime'); diff --git a/public/app/features/variables/textbox/actions.ts b/public/app/features/variables/textbox/actions.ts index 4e10c499168..c01d82948ff 100644 --- a/public/app/features/variables/textbox/actions.ts +++ b/public/app/features/variables/textbox/actions.ts @@ -1,14 +1,16 @@ -import { TextBoxVariableModel } from '../types'; -import { ThunkResult } from '../../../types'; -import { getVariable } from '../state/selectors'; -import { variableAdapters } from '../adapters'; -import { createTextBoxOptions } from './reducer'; -import { KeyedVariableIdentifier } from '../state/types'; -import { setOptionFromUrl } from '../state/actions'; import { UrlQueryValue } from '@grafana/data'; -import { changeVariableProp } from '../state/sharedReducer'; -import { ensureStringValues, toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + +import { ThunkResult } from '../../../types'; +import { variableAdapters } from '../adapters'; +import { setOptionFromUrl } from '../state/actions'; import { toKeyedAction } from '../state/keyedVariablesReducer'; +import { getVariable } from '../state/selectors'; +import { changeVariableProp } from '../state/sharedReducer'; +import { KeyedVariableIdentifier } from '../state/types'; +import { TextBoxVariableModel } from '../types'; +import { ensureStringValues, toKeyedVariableIdentifier, toVariablePayload } from '../utils'; + +import { createTextBoxOptions } from './reducer'; export const updateTextBoxVariableOptions = (identifier: KeyedVariableIdentifier): ThunkResult => { return async (dispatch, getState) => { diff --git a/public/app/features/variables/textbox/adapter.test.ts b/public/app/features/variables/textbox/adapter.test.ts index f28b7fd5c7c..93098078ad9 100644 --- a/public/app/features/variables/textbox/adapter.test.ts +++ b/public/app/features/variables/textbox/adapter.test.ts @@ -1,8 +1,9 @@ import { variableAdapters } from '../adapters'; -import { createTextBoxVariableAdapter } from './adapter'; import { textboxBuilder } from '../shared/testing/builders'; import { VariableHide } from '../types'; +import { createTextBoxVariableAdapter } from './adapter'; + variableAdapters.setInit(() => [createTextBoxVariableAdapter()]); describe('createTextBoxVariableAdapter', () => { diff --git a/public/app/features/variables/textbox/adapter.ts b/public/app/features/variables/textbox/adapter.ts index a916520eaca..9b6784ccd9d 100644 --- a/public/app/features/variables/textbox/adapter.ts +++ b/public/app/features/variables/textbox/adapter.ts @@ -1,15 +1,16 @@ import { cloneDeep } from 'lodash'; -import { TextBoxVariableModel } from '../types'; -import { initialTextBoxVariableModelState, textBoxVariableReducer } from './reducer'; import { dispatch } from '../../../store/store'; -import { setOptionAsCurrent } from '../state/actions'; import { VariableAdapter } from '../adapters'; -import { TextBoxVariablePicker } from './TextBoxVariablePicker'; -import { TextBoxVariableEditor } from './TextBoxVariableEditor'; -import { setTextBoxVariableOptionsFromUrl, updateTextBoxVariableOptions } from './actions'; +import { setOptionAsCurrent } from '../state/actions'; +import { TextBoxVariableModel } from '../types'; import { toKeyedVariableIdentifier } from '../utils'; +import { TextBoxVariableEditor } from './TextBoxVariableEditor'; +import { TextBoxVariablePicker } from './TextBoxVariablePicker'; +import { setTextBoxVariableOptionsFromUrl, updateTextBoxVariableOptions } from './actions'; +import { initialTextBoxVariableModelState, textBoxVariableReducer } from './reducer'; + export const createTextBoxVariableAdapter = (): VariableAdapter => { return { id: 'textbox', diff --git a/public/app/features/variables/textbox/reducer.test.ts b/public/app/features/variables/textbox/reducer.test.ts index fbd83a44318..d8ff2eacc22 100644 --- a/public/app/features/variables/textbox/reducer.test.ts +++ b/public/app/features/variables/textbox/reducer.test.ts @@ -1,12 +1,14 @@ -import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { cloneDeep } from 'lodash'; + +import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { getVariableTestContext } from '../state/helpers'; import { VariablesState } from '../state/types'; -import { createTextBoxOptions, textBoxVariableReducer } from './reducer'; import { TextBoxVariableModel } from '../types'; -import { createTextBoxVariableAdapter } from './adapter'; import { toVariablePayload } from '../utils'; +import { createTextBoxVariableAdapter } from './adapter'; +import { createTextBoxOptions, textBoxVariableReducer } from './reducer'; + describe('textBoxVariableReducer', () => { const adapter = createTextBoxVariableAdapter(); diff --git a/public/app/features/variables/textbox/reducer.ts b/public/app/features/variables/textbox/reducer.ts index 405cde2da91..9195c3ea456 100644 --- a/public/app/features/variables/textbox/reducer.ts +++ b/public/app/features/variables/textbox/reducer.ts @@ -1,8 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { initialVariableModelState, TextBoxVariableModel, VariableOption } from '../types'; -import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; import { getInstanceState } from '../state/selectors'; +import { initialVariablesState, VariablePayload, VariablesState } from '../state/types'; +import { initialVariableModelState, TextBoxVariableModel, VariableOption } from '../types'; export const initialTextBoxVariableModelState: TextBoxVariableModel = { ...initialVariableModelState, diff --git a/public/app/features/variables/types.ts b/public/app/features/variables/types.ts index 5d894ab35b8..22d7a35bf6c 100644 --- a/public/app/features/variables/types.ts +++ b/public/app/features/variables/types.ts @@ -1,4 +1,5 @@ import { ComponentType } from 'react'; + import { BusEventWithPayload, DataQuery, diff --git a/public/app/features/variables/utils.test.ts b/public/app/features/variables/utils.test.ts index a7b2ca13f89..4c22a0fc842 100644 --- a/public/app/features/variables/utils.test.ts +++ b/public/app/features/variables/utils.test.ts @@ -1,3 +1,6 @@ +import { UrlQueryMap } from '@grafana/data'; + +import { VariableRefresh } from './types'; import { containsVariable, ensureStringValues, @@ -6,8 +9,6 @@ import { getVariableRefresh, isAllVariable, } from './utils'; -import { VariableRefresh } from './types'; -import { UrlQueryMap } from '@grafana/data'; describe('isAllVariable', () => { it.each` diff --git a/public/app/features/variables/utils.ts b/public/app/features/variables/utils.ts index dfe00845608..9c555024cf7 100644 --- a/public/app/features/variables/utils.ts +++ b/public/app/features/variables/utils.ts @@ -1,16 +1,18 @@ import { isArray, isEqual } from 'lodash'; + import { ScopedVars, UrlQueryMap, UrlQueryValue, VariableType } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; - -import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from './constants'; -import { QueryVariableModel, TransactionStatus, VariableModel, VariableRefresh } from './types'; -import { getTimeSrv } from '../dashboard/services/TimeSrv'; -import { variableAdapters } from './adapters'; import { safeStringifyValue } from 'app/core/utils/explore'; -import { StoreState } from '../../types'; + import { getState } from '../../store/store'; +import { StoreState } from '../../types'; +import { getTimeSrv } from '../dashboard/services/TimeSrv'; + +import { variableAdapters } from './adapters'; +import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from './constants'; import { getVariablesState } from './state/selectors'; import { KeyedVariableIdentifier, VariableIdentifier, VariablePayload } from './state/types'; +import { QueryVariableModel, TransactionStatus, VariableModel, VariableRefresh } from './types'; /* * This regex matches 3 types of variable reference with an optional format specifier diff --git a/public/app/plugins/datasource/alertmanager/ConfigEditor.tsx b/public/app/plugins/datasource/alertmanager/ConfigEditor.tsx index 86c92977ea9..fdb4710782e 100644 --- a/public/app/plugins/datasource/alertmanager/ConfigEditor.tsx +++ b/public/app/plugins/datasource/alertmanager/ConfigEditor.tsx @@ -1,6 +1,8 @@ +import React from 'react'; + import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data'; import { DataSourceHttpSettings, InlineFormLabel, Select } from '@grafana/ui'; -import React from 'react'; + import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from './types'; export type Props = DataSourcePluginOptionsEditorProps; diff --git a/public/app/plugins/datasource/alertmanager/DataSource.ts b/public/app/plugins/datasource/alertmanager/DataSource.ts index 4ccb773c3e5..e091184a458 100644 --- a/public/app/plugins/datasource/alertmanager/DataSource.ts +++ b/public/app/plugins/datasource/alertmanager/DataSource.ts @@ -1,6 +1,8 @@ import { lastValueFrom, Observable, of } from 'rxjs'; + import { DataQuery, DataQueryResponse, DataSourceApi, DataSourceInstanceSettings } from '@grafana/data'; import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime'; + import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from './types'; export type AlertManagerQuery = { diff --git a/public/app/plugins/datasource/alertmanager/module.ts b/public/app/plugins/datasource/alertmanager/module.ts index b8bb42a2b97..a79d84f71e0 100644 --- a/public/app/plugins/datasource/alertmanager/module.ts +++ b/public/app/plugins/datasource/alertmanager/module.ts @@ -1,4 +1,5 @@ import { DataSourcePlugin } from '@grafana/data'; + import { ConfigEditor } from './ConfigEditor'; import { AlertManagerDatasource } from './DataSource'; diff --git a/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts b/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts index b8132e06bae..d787b538b3e 100644 --- a/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts +++ b/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts @@ -1,8 +1,9 @@ import { isString } from 'lodash'; + +import { SelectableValue } from '@grafana/data'; + import { ALIGNMENT_PERIODS, SELECTORS } from './constants'; import CloudMonitoringDatasource from './datasource'; -import { CloudMonitoringVariableQuery, MetricDescriptor, MetricFindQueryTypes, MetricKind, ValueTypes } from './types'; -import { SelectableValue } from '@grafana/data'; import { extractServicesFromMetricDescriptors, getAggregationOptionsByMetric, @@ -10,6 +11,7 @@ import { getLabelKeys, getMetricTypesByService, } from './functions'; +import { CloudMonitoringVariableQuery, MetricDescriptor, MetricFindQueryTypes, MetricKind, ValueTypes } from './types'; export default class CloudMonitoringMetricFindQuery { constructor(private datasource: CloudMonitoringDatasource) {} diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx index aa5b3e16202..02fd2285a08 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import { shallow } from 'enzyme'; -import { Select } from '@grafana/ui'; -import { Aggregation, Props } from './Aggregation'; -import { ValueTypes, MetricKind } from '../types'; +import React from 'react'; import { TemplateSrvStub } from 'test/specs/helpers'; +import { Select } from '@grafana/ui'; + +import { ValueTypes, MetricKind } from '../types'; + +import { Aggregation, Props } from './Aggregation'; + const props: Props = { onChange: () => {}, // @ts-ignore diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx index 0e14f821052..9ca3bf37fa7 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx @@ -1,11 +1,13 @@ -import { SelectableValue } from '@grafana/data'; -import { Select } from '@grafana/ui'; import React, { FC, useMemo } from 'react'; -import { QueryEditorField } from '.'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { getAggregationOptionsByMetric } from '../functions'; import { MetricDescriptor, MetricKind, ValueTypes } from '../types'; +import { QueryEditorField } from '.'; + export interface Props { refId: string; onChange: (metricDescriptor: string) => void; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx index edd714da303..1c0a134fa38 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AliasBy.tsx @@ -1,10 +1,12 @@ -import { Input } from '@grafana/ui'; import { debounce } from 'lodash'; import React, { FunctionComponent, useState } from 'react'; -import { QueryEditorRow } from '.'; +import { Input } from '@grafana/ui'; + import { INPUT_WIDTH } from '../constants'; +import { QueryEditorRow } from '.'; + export interface Props { refId: string; onChange: (alias: any) => void; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx index d2fd6862e40..7d4226183df 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx @@ -1,11 +1,13 @@ -import { SelectableValue } from '@grafana/data'; import React, { FC } from 'react'; -import { AlignmentFunction, AlignmentPeriod, AlignmentPeriodLabel, QueryEditorField, QueryEditorRow } from '.'; +import { SelectableValue } from '@grafana/data'; + import { SELECT_WIDTH } from '../constants'; import CloudMonitoringDatasource from '../datasource'; import { CustomMetaData, MetricQuery, SLOQuery } from '../types'; +import { AlignmentFunction, AlignmentPeriod, AlignmentPeriodLabel, QueryEditorField, QueryEditorRow } from '.'; + export interface Props { refId: string; onChange: (query: MetricQuery | SLOQuery) => void; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx index 0c634f1cc4f..fff6b5f09f0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx @@ -1,6 +1,7 @@ +import React, { FC, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; -import React, { FC, useMemo } from 'react'; import { SELECT_WIDTH } from '../constants'; import { getAlignmentPickerData } from '../functions'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriod.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriod.tsx index 11afb9ea863..9e75ba2bbaf 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriod.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriod.tsx @@ -1,6 +1,7 @@ +import React, { useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; -import React, { useMemo } from 'react'; import { ALIGNMENT_PERIODS } from '../constants'; import { MetricQuery, SLOQuery } from '../types'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriodLabel.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriodLabel.tsx index 2a4d1651481..1bfcf1f686a 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriodLabel.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentPeriodLabel.tsx @@ -1,5 +1,7 @@ import React, { FC, useMemo } from 'react'; + import { rangeUtil } from '@grafana/data'; + import { ALIGNMENTS } from '../constants'; import CloudMonitoringDatasource from '../datasource'; import { CustomMetaData } from '../types'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx index b44f4a7e23f..fa24c8ca8d0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx @@ -1,10 +1,12 @@ +import React from 'react'; + import { SelectableValue, toOption } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; import { LegacyForms } from '@grafana/ui'; -import React from 'react'; import CloudMonitoringDatasource from '../datasource'; import { AnnotationTarget, EditorMode, MetricDescriptor, MetricKind } from '../types'; + import { AnnotationsHelp, LabelFilter, Metrics, Project, QueryEditorRow } from './'; const { Input } = LegacyForms; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx b/public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx index 5f281cec009..cde823d4ac2 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx @@ -1,6 +1,8 @@ -import React, { PureComponent } from 'react'; -import { QueryEditorHelpProps } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + +import { QueryEditorHelpProps } from '@grafana/data'; + import { CloudMonitoringQuery } from '../types'; export default class CloudMonitoringCheatSheet extends PureComponent< diff --git a/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx index f710279fa9e..8378bfb0e19 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx @@ -1,8 +1,10 @@ import React, { PureComponent } from 'react'; + import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; -import { CloudMonitoringOptions, CloudMonitoringSecureJsonData } from '../../types'; import { ConnectionConfig } from '@grafana/google-sdk'; +import { CloudMonitoringOptions, CloudMonitoringSecureJsonData } from '../../types'; + export type Props = DataSourcePluginOptionsEditorProps; export class ConfigEditor extends PureComponent { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Fields.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Fields.tsx index dc612eb735c..e18b176402c 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Fields.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Fields.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { SelectableValue } from '@grafana/data'; import { HorizontalGroup, InlineField, InlineLabel, PopoverContent, Select } from '@grafana/ui'; -import React, { FC } from 'react'; import { INNER_LABEL_WIDTH, LABEL_WIDTH } from '../constants'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx index 8810d99f936..72357a567c2 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx @@ -1,12 +1,14 @@ -import { SelectableValue } from '@grafana/data'; -import { MultiSelect } from '@grafana/ui'; import React, { FunctionComponent, useMemo } from 'react'; -import { Aggregation, QueryEditorRow } from '.'; +import { SelectableValue } from '@grafana/data'; +import { MultiSelect } from '@grafana/ui'; + import { INPUT_WIDTH, SYSTEM_LABELS } from '../constants'; import { labelsToGroupedOptions } from '../functions'; import { MetricDescriptor, MetricQuery } from '../types'; +import { Aggregation, QueryEditorRow } from '.'; + export interface Props { refId: string; variableOptionGroup: SelectableValue; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx b/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx index a0f843a5302..194d94947fb 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/LabelFilter.tsx @@ -1,14 +1,16 @@ -import { SelectableValue, toOption } from '@grafana/data'; -import { Button, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui'; -import { CustomControlProps } from '@grafana/ui/src/components/Select/types'; import { flatten } from 'lodash'; import React, { FunctionComponent, useCallback, useMemo } from 'react'; -import { QueryEditorRow } from '.'; +import { SelectableValue, toOption } from '@grafana/data'; +import { Button, HorizontalGroup, Select, VerticalGroup } from '@grafana/ui'; +import { CustomControlProps } from '@grafana/ui/src/components/Select/types'; + import { SELECT_WIDTH } from '../constants'; import { labelsToGroupedOptions, stringArrayToFilters } from '../functions'; import { Filter } from '../types'; +import { QueryEditorRow } from '.'; + export interface Props { labels: { [key: string]: string[] }; filters: string[]; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx index a992c2733e4..48cbd630f49 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/MQLQueryEditor.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { TextArea } from '@grafana/ui'; export interface Props { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx index 31b262b987a..d6ec09e9941 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx @@ -1,7 +1,7 @@ -import { SelectableValue } from '@grafana/data'; import React, { useCallback, useEffect, useState } from 'react'; -import { AliasBy, Project, VisualMetricQueryEditor } from '.'; +import { SelectableValue } from '@grafana/data'; + import CloudMonitoringDatasource from '../datasource'; import { getAlignmentPickerData } from '../functions'; import { @@ -15,8 +15,11 @@ import { SLOQuery, ValueTypes, } from '../types'; + import { MQLQueryEditor } from './MQLQueryEditor'; +import { AliasBy, Project, VisualMetricQueryEditor } from '.'; + export interface Props { refId: string; customMetaData: CustomMetaData; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx index cda52b2590d..3f39b3ea3c2 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Metrics.tsx @@ -1,15 +1,17 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { TemplateSrv } from '@grafana/runtime'; -import { getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui'; import { startCase, uniqBy } from 'lodash'; import React, { useCallback, useEffect, useState } from 'react'; -import { QueryEditorField, QueryEditorRow } from '.'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { TemplateSrv } from '@grafana/runtime'; +import { getSelectStyles, Select, useStyles2, useTheme2 } from '@grafana/ui'; + import { INNER_LABEL_WIDTH, LABEL_WIDTH, SELECT_WIDTH } from '../constants'; import CloudMonitoringDatasource from '../datasource'; import { MetricDescriptor } from '../types'; +import { QueryEditorField, QueryEditorRow } from '.'; + export interface Props { refId: string; onChange: (metricDescriptor: MetricDescriptor) => void; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx index 1edba340702..1dc72c44ef6 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx @@ -1,8 +1,11 @@ import React, { FunctionComponent, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { RadioButtonGroup } from '@grafana/ui'; -import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../types'; + import { getAlignmentPickerData } from '../functions'; +import { MetricDescriptor, MetricKind, MetricQuery, PreprocessorType, ValueTypes } from '../types'; + import { QueryEditorRow } from '.'; const NONE_OPTION = { label: 'None', value: PreprocessorType.None }; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx index 72ff1829ccc..8f0fb7c9291 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Project.tsx @@ -1,11 +1,13 @@ -import { SelectableValue } from '@grafana/data'; -import { Select } from '@grafana/ui'; import React, { useEffect, useMemo, useState } from 'react'; -import { QueryEditorRow } from '.'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { SELECT_WIDTH } from '../constants'; import CloudMonitoringDatasource from '../datasource'; +import { QueryEditorRow } from '.'; + export interface Props { refId: string; datasource: CloudMonitoringDatasource; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx index a4124efbd09..bd2fd416c79 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx @@ -1,15 +1,18 @@ import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + import { QueryEditorProps, toOption } from '@grafana/data'; import { Button, Select } from '@grafana/ui'; -import React, { PureComponent } from 'react'; import { QUERY_TYPES, SELECT_WIDTH } from '../constants'; import CloudMonitoringDatasource from '../datasource'; import { CloudMonitoringQuery, EditorMode, MetricQuery, QueryType, SLOQuery, CloudMonitoringOptions } from '../types'; -import { MetricQueryEditor, QueryEditorRow, SLOQueryEditor } from './'; + import { defaultQuery } from './MetricQueryEditor'; import { defaultQuery as defaultSLOQuery } from './SLO/SLOQueryEditor'; +import { MetricQueryEditor, QueryEditorRow, SLOQueryEditor } from './'; + export type Props = QueryEditorProps; export class QueryEditor extends PureComponent { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLO.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLO.tsx index 064e7f5ae31..c62d8246095 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLO.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLO.tsx @@ -1,6 +1,7 @@ +import React, { useEffect, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; -import React, { useEffect, useState } from 'react'; import { QueryEditorRow } from '..'; import { SELECT_WIDTH } from '../../constants'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLOQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLOQueryEditor.tsx index 2605c007e6a..f33fe4e826b 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLOQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/SLO/SLOQueryEditor.tsx @@ -1,12 +1,14 @@ -import { SelectableValue } from '@grafana/data'; import React from 'react'; -import { Selector, Service, SLO } from '.'; +import { SelectableValue } from '@grafana/data'; + import { AliasBy, AlignmentPeriod, AlignmentPeriodLabel, Project, QueryEditorRow } from '..'; import { SELECT_WIDTH } from '../../constants'; import CloudMonitoringDatasource from '../../datasource'; import { AlignmentTypes, CustomMetaData, SLOQuery } from '../../types'; +import { Selector, Service, SLO } from '.'; + export interface Props { refId: string; customMetaData: CustomMetaData; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLO/Selector.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLO/Selector.tsx index 4357501b29d..02f4a066d14 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/SLO/Selector.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/SLO/Selector.tsx @@ -1,6 +1,7 @@ +import React from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; -import React from 'react'; import { QueryEditorRow } from '..'; import { SELECT_WIDTH, SELECTORS } from '../../constants'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLO/Service.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLO/Service.tsx index a5d5c433d28..ef158a7ce70 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/SLO/Service.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/SLO/Service.tsx @@ -1,6 +1,7 @@ +import React, { useEffect, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; -import React, { useEffect, useState } from 'react'; import { QueryEditorRow } from '..'; import { SELECT_WIDTH } from '../../constants'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx index 8012edb6e97..8b4bd47c29a 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx @@ -1,11 +1,14 @@ import React from 'react'; // @ts-ignore import renderer from 'react-test-renderer'; -import { CloudMonitoringVariableQueryEditor, Props } from './VariableQueryEditor'; -import { CloudMonitoringVariableQuery, MetricFindQueryTypes } from '../types'; -import CloudMonitoringDatasource from '../datasource'; + import { VariableModel } from '@grafana/data'; +import CloudMonitoringDatasource from '../datasource'; +import { CloudMonitoringVariableQuery, MetricFindQueryTypes } from '../types'; + +import { CloudMonitoringVariableQueryEditor, Props } from './VariableQueryEditor'; + jest.mock('../functions', () => ({ getMetricTypes: (): any => ({ metricTypes: [], selectedMetricType: '' }), extractServicesFromMetricDescriptors: (): any[] => [], diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx index 9cb564fb573..2c7458c149e 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx @@ -1,9 +1,9 @@ import React, { PureComponent } from 'react'; -import { getTemplateSrv } from '@grafana/runtime'; import { QueryEditorProps } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; -import { VariableQueryField } from './'; +import CloudMonitoringDatasource from '../datasource'; import { extractServicesFromMetricDescriptors, getLabelKeys, getMetricTypes } from '../functions'; import { CloudMonitoringOptions, @@ -13,7 +13,8 @@ import { MetricFindQueryTypes, VariableQueryData, } from '../types'; -import CloudMonitoringDatasource from '../datasource'; + +import { VariableQueryField } from './'; export type Props = QueryEditorProps< CloudMonitoringDatasource, diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx index c48ce93baf4..79bb0b228c8 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx @@ -1,10 +1,12 @@ -import { SelectableValue } from '@grafana/data'; import React from 'react'; -import { Alignment, GroupBy, LabelFilter, Metrics, Preprocessor } from '.'; +import { SelectableValue } from '@grafana/data'; + import CloudMonitoringDatasource from '../datasource'; import { CustomMetaData, MetricDescriptor, MetricQuery, SLOQuery } from '../types'; +import { Alignment, GroupBy, LabelFilter, Metrics, Preprocessor } from '.'; + export interface Props { refId: string; customMetaData: CustomMetaData; diff --git a/public/app/plugins/datasource/cloud-monitoring/constants.ts b/public/app/plugins/datasource/cloud-monitoring/constants.ts index e87bcd7e092..df640dcaabf 100644 --- a/public/app/plugins/datasource/cloud-monitoring/constants.ts +++ b/public/app/plugins/datasource/cloud-monitoring/constants.ts @@ -1,6 +1,7 @@ -import { MetricKind, QueryType, ValueTypes } from './types'; import { GoogleAuthType } from '@grafana/google-sdk'; +import { MetricKind, QueryType, ValueTypes } from './types'; + // not super excited about using uneven numbers, but this makes it align perfectly with rows that has two fields export const INPUT_WIDTH = 71; export const LABEL_WIDTH = 19; diff --git a/public/app/plugins/datasource/cloud-monitoring/datasource.ts b/public/app/plugins/datasource/cloud-monitoring/datasource.ts index 7e75a680907..d1e84261f16 100644 --- a/public/app/plugins/datasource/cloud-monitoring/datasource.ts +++ b/public/app/plugins/datasource/cloud-monitoring/datasource.ts @@ -1,6 +1,7 @@ import { chunk, flatten, isString, isArray } from 'lodash'; import { from, lastValueFrom, Observable, of } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; + import { DataQueryRequest, DataQueryResponse, @@ -9,9 +10,9 @@ import { SelectableValue, } from '@grafana/data'; import { DataSourceWithBackend, getBackendSrv, toDataQueryResponse } from '@grafana/runtime'; - -import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; + import { CloudMonitoringOptions, CloudMonitoringQuery, diff --git a/public/app/plugins/datasource/cloud-monitoring/functions.ts b/public/app/plugins/datasource/cloud-monitoring/functions.ts index 6c37c962ec6..0dd7bc46ed0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/functions.ts +++ b/public/app/plugins/datasource/cloud-monitoring/functions.ts @@ -1,6 +1,7 @@ -import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { chunk, initial, startCase, uniqBy } from 'lodash'; +import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; + import { AGGREGATIONS, ALIGNMENTS, SYSTEM_LABELS } from './constants'; import CloudMonitoringDatasource from './datasource'; import { AlignmentTypes, MetricDescriptor, MetricKind, PreprocessorType, ValueTypes } from './types'; diff --git a/public/app/plugins/datasource/cloud-monitoring/module.ts b/public/app/plugins/datasource/cloud-monitoring/module.ts index 32508fe8efb..64f25908f59 100644 --- a/public/app/plugins/datasource/cloud-monitoring/module.ts +++ b/public/app/plugins/datasource/cloud-monitoring/module.ts @@ -1,11 +1,11 @@ import { DataSourcePlugin } from '@grafana/data'; -import CloudMonitoringDatasource from './datasource'; -import { QueryEditor } from './components/QueryEditor'; -import { ConfigEditor } from './components/ConfigEditor/ConfigEditor'; -import CloudMonitoringCheatSheet from './components/CloudMonitoringCheatSheet'; import { CloudMonitoringAnnotationsQueryCtrl } from './annotations_query_ctrl'; +import CloudMonitoringCheatSheet from './components/CloudMonitoringCheatSheet'; +import { ConfigEditor } from './components/ConfigEditor/ConfigEditor'; +import { QueryEditor } from './components/QueryEditor'; import { CloudMonitoringVariableQueryEditor } from './components/VariableQueryEditor'; +import CloudMonitoringDatasource from './datasource'; import { CloudMonitoringQuery } from './types'; export const plugin = new DataSourcePlugin(CloudMonitoringDatasource) diff --git a/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts b/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts index a223de820ce..9cfc69d10e0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/specs/datasource.test.ts @@ -1,14 +1,15 @@ import { of, throwError } from 'rxjs'; -import { DataSourceInstanceSettings, toUtc } from '@grafana/data'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; -import CloudMonitoringDataSource from '../datasource'; -import { TemplateSrv } from 'app/features/templating/template_srv'; -import { CloudMonitoringOptions } from '../types'; +import { DataSourceInstanceSettings, toUtc } from '@grafana/data'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { CustomVariableModel } from '../../../../features/variables/types'; +import { TemplateSrv } from 'app/features/templating/template_srv'; + import { initialCustomVariableModelState } from '../../../../features/variables/custom/reducer'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; +import { CustomVariableModel } from '../../../../features/variables/types'; +import CloudMonitoringDataSource from '../datasource'; +import { CloudMonitoringOptions } from '../types'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/plugins/datasource/cloud-monitoring/variables.ts b/public/app/plugins/datasource/cloud-monitoring/variables.ts index 181e9f07a82..0dfb2ea49e0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/variables.ts +++ b/public/app/plugins/datasource/cloud-monitoring/variables.ts @@ -1,11 +1,12 @@ import { from, Observable } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; + import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@grafana/data'; -import CloudMonitoringDatasource from './datasource'; -import { CloudMonitoringVariableQuery } from './types'; import CloudMonitoringMetricFindQuery from './CloudMonitoringMetricFindQuery'; import { CloudMonitoringVariableQueryEditor } from './components/VariableQueryEditor'; +import CloudMonitoringDatasource from './datasource'; +import { CloudMonitoringVariableQuery } from './types'; export class CloudMonitoringVariableSupport extends CustomVariableSupport< CloudMonitoringDatasource, diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts index e5701c7898b..7cc2ab389e1 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts @@ -1,9 +1,10 @@ +import { of } from 'rxjs'; + import { dateTime } from '@grafana/data'; import { setBackendSrv } from '@grafana/runtime'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { initialCustomVariableModelState } from 'app/features/variables/custom/reducer'; import { CustomVariableModel } from 'app/features/variables/types'; -import { of } from 'rxjs'; import { TemplateSrvMock } from '../../../../features/templating/template_srv.mock'; import { CloudWatchDatasource } from '../datasource'; @@ -46,7 +47,7 @@ export function setupMockedDataSource({ data = [], variables }: { data?: any; va const fetchMock = jest.fn().mockReturnValue(of({ data })); setBackendSrv({ fetch: fetchMock } as any); - return { datasource, fetchMock }; + return { datasource, fetchMock, templateService }; } export const metricVariable: CustomVariableModel = { @@ -124,3 +125,19 @@ export const aggregationvariable: CustomVariableModel = { ], multi: false, }; + +export const dimensionVariable: CustomVariableModel = { + ...initialCustomVariableModelState, + id: 'dimension', + name: 'dimension', + current: { + value: 'env', + text: 'env', + selected: true, + }, + options: [ + { value: 'env', text: 'env', selected: false }, + { value: 'tag', text: 'tag', selected: false }, + ], + multi: false, +}; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts index 42a6c7f5d2f..db6d58c52ff 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts @@ -1,4 +1,5 @@ import { monacoTypes } from '@grafana/ui'; + import { Monaco } from '../../monarch/types'; import * as SQLTestData from '../cloudwatch-sql-test-data'; import * as MetricMathTestData from '../metric-math-test-data'; diff --git a/public/app/plugins/datasource/cloudwatch/annotations_query_ctrl.ts b/public/app/plugins/datasource/cloudwatch/annotations_query_ctrl.ts index 360c8cce6a6..93ee1f1c19f 100644 --- a/public/app/plugins/datasource/cloudwatch/annotations_query_ctrl.ts +++ b/public/app/plugins/datasource/cloudwatch/annotations_query_ctrl.ts @@ -1,4 +1,5 @@ import { defaultsDeep } from 'lodash'; + import { CloudWatchAnnotationQuery } from './types'; export class CloudWatchAnnotationsQueryCtrl { diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.test.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.test.ts index fb757d3b150..765ed6f3abb 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.test.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.test.ts @@ -1,6 +1,5 @@ import { TemplateSrv } from 'app/features/templating/template_srv'; -import { QueryEditorExpressionType } from '../expressions'; -import { SQLExpression } from '../types'; + import { aggregationvariable, labelsVariable, @@ -15,6 +14,9 @@ import { createFunction, createProperty, } from '../__mocks__/sqlUtils'; +import { QueryEditorExpressionType } from '../expressions'; +import { SQLExpression } from '../types'; + import SQLGenerator from './SQLGenerator'; describe('SQLGenerator', () => { diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.ts index ba47ea4df17..3b04844200e 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/SQLGenerator.ts @@ -1,5 +1,5 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -import { SQLExpression } from '../types'; + import { QueryEditorArrayExpression, QueryEditorExpression, @@ -8,6 +8,7 @@ import { QueryEditorOperatorExpression, QueryEditorPropertyExpression, } from '../expressions'; +import { SQLExpression } from '../types'; export default class SQLGenerator { constructor(private templateSrv: TemplateSrv = getTemplateSrv()) {} diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/CompletionItemProvider.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/CompletionItemProvider.ts index 64842ca96b5..ef347cf42d9 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/CompletionItemProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/CompletionItemProvider.ts @@ -1,9 +1,13 @@ -import type { Monaco, monacoTypes } from '@grafana/ui'; import { uniq } from 'lodash'; -import { TRIGGER_SUGGEST } from '../../monarch/commands'; + +import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; +import type { Monaco, monacoTypes } from '@grafana/ui'; + +import { CloudWatchDatasource } from '../../datasource'; +import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { LinkedToken } from '../../monarch/LinkedToken'; +import { TRIGGER_SUGGEST } from '../../monarch/commands'; import { SuggestionKind, CompletionItemPriority, StatementPosition } from '../../monarch/types'; -import { SQLTokenTypes } from './types'; import { BY, FROM, @@ -19,12 +23,11 @@ import { LOGICAL_OPERATORS, STATISTICS, } from '../language'; -import { getMetricNameToken, getNamespaceToken } from './tokenUtils'; -import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; -import { CloudWatchDatasource } from '../../datasource'; -import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; + import { getStatementPosition } from './statementPosition'; import { getSuggestionKinds } from './suggestionKind'; +import { getMetricNameToken, getNamespaceToken } from './tokenUtils'; +import { SQLTokenTypes } from './types'; type CompletionItem = monacoTypes.languages.CompletionItem; diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.test.ts index 878411e3954..40ed83ee21a 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.test.ts @@ -1,16 +1,18 @@ import { monacoTypes } from '@grafana/ui'; -import MonacoMock from '../../__mocks__/monarch/Monaco'; -import TextModel from '../../__mocks__/monarch/TextModel'; + import { multiLineFullQuery, singleLineFullQuery, singleLineEmptyQuery, singleLineTwoQueries, } from '../../__mocks__/cloudwatch-sql-test-data'; +import MonacoMock from '../../__mocks__/monarch/Monaco'; +import TextModel from '../../__mocks__/monarch/TextModel'; import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; import { StatementPosition } from '../../monarch/types'; -import { getStatementPosition } from './statementPosition'; import cloudWatchSqlLanguageDefinition from '../definition'; + +import { getStatementPosition } from './statementPosition'; import { SQLTokenTypes } from './types'; describe('statementPosition', () => { diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.ts index b1af5f11d09..91e8c3abdc1 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/statementPosition.ts @@ -1,6 +1,7 @@ -import { AND, ASC, BY, DESC, EQUALS, FROM, GROUP, NOT_EQUALS, ORDER, SCHEMA, SELECT, WHERE } from '../language'; import { LinkedToken } from '../../monarch/LinkedToken'; import { StatementPosition } from '../../monarch/types'; +import { AND, ASC, BY, DESC, EQUALS, FROM, GROUP, NOT_EQUALS, ORDER, SCHEMA, SELECT, WHERE } from '../language'; + import { SQLTokenTypes } from './types'; export function getStatementPosition(currentToken: LinkedToken | null): StatementPosition { diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.test.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.test.ts index 762162864d5..a1bab84f7a3 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.test.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.test.ts @@ -1,18 +1,20 @@ import { monacoTypes } from '@grafana/ui'; -import MonacoMock from '../../__mocks__/monarch/Monaco'; -import TextModel from '../../__mocks__/monarch/TextModel'; + import { multiLineFullQuery, singleLineFullQuery, singleLineTwoQueries, multiLineIncompleteQueryWithoutNamespace, } from '../../__mocks__/cloudwatch-sql-test-data'; +import MonacoMock from '../../__mocks__/monarch/Monaco'; +import TextModel from '../../__mocks__/monarch/TextModel'; import { LinkedToken } from '../../monarch/LinkedToken'; import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; -import { SQLTokenTypes } from './types'; -import { getMetricNameToken, getNamespaceToken, getSelectStatisticToken, getSelectToken } from './tokenUtils'; -import { SELECT } from '../language'; import cloudWatchSqlLanguageDefinition from '../definition'; +import { SELECT } from '../language'; + +import { getMetricNameToken, getNamespaceToken, getSelectStatisticToken, getSelectToken } from './tokenUtils'; +import { SQLTokenTypes } from './types'; const getToken = ( query: string, diff --git a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.ts b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.ts index d9a1762efe4..820c1c4d481 100644 --- a/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.ts +++ b/public/app/plugins/datasource/cloudwatch/cloudwatch-sql/completion/tokenUtils.ts @@ -1,5 +1,6 @@ import { LinkedToken } from '../../monarch/LinkedToken'; import { FROM, SCHEMA, SELECT } from '../language'; + import { SQLTokenTypes } from './types'; export const getSelectToken = (currentToken: LinkedToken | null) => diff --git a/public/app/plugins/datasource/cloudwatch/components/Alias.tsx b/public/app/plugins/datasource/cloudwatch/components/Alias.tsx index 3b1ead48981..2f3ddd8d8b5 100644 --- a/public/app/plugins/datasource/cloudwatch/components/Alias.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Alias.tsx @@ -1,5 +1,6 @@ -import React, { FunctionComponent, useState } from 'react'; import { debounce } from 'lodash'; +import React, { FunctionComponent, useState } from 'react'; + import { Input } from '@grafana/ui'; export interface Props { diff --git a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.test.tsx index ea0fec67e6d..be67800951f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.test.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import '@testing-library/jest-dom'; import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; import { CloudWatchAnnotationQuery } from '../types'; + import { AnnotationQueryEditor } from './AnnotationQueryEditor'; const ds = setupMockedDataSource({ diff --git a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.tsx index 099aa55ff41..72bb082d99e 100644 --- a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor.tsx @@ -1,10 +1,13 @@ import React, { ChangeEvent } from 'react'; + import { PanelData } from '@grafana/data'; import { EditorField, EditorHeader, EditorRow, InlineSelect, Space } from '@grafana/experimental'; import { Input, Switch } from '@grafana/ui'; + import { CloudWatchDatasource } from '../datasource'; import { useRegions } from '../hooks'; import { CloudWatchAnnotationQuery, CloudWatchMetricsQuery } from '../types'; + import { MetricStatEditor } from './MetricStatEditor'; export type Props = { diff --git a/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx b/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx index 41035992ab1..e8324be8989 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/CloudWatchLink.tsx @@ -1,10 +1,11 @@ import React, { Component } from 'react'; -import { CloudWatchLogsQuery } from '../types'; import { PanelData } from '@grafana/data'; import { Icon } from '@grafana/ui'; + import { encodeUrl, AwsUrl } from '../aws_url'; import { CloudWatchDatasource } from '../datasource'; +import { CloudWatchLogsQuery } from '../types'; interface Props { query: CloudWatchLogsQuery; diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx index be8613e29ab..3947939384f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { AwsAuthType } from '@grafana/aws-sdk'; + import { ConfigEditor, Props } from './ConfigEditor'; jest.mock('app/features/plugins/datasource_srv', () => ({ diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx index 3179bdc7d9c..5dc682a3a36 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor.tsx @@ -1,21 +1,22 @@ import React, { FC, useEffect, useState } from 'react'; import { useDebounce } from 'react-use'; -import { Input, InlineField } from '@grafana/ui'; + +import { ConnectionConfig } from '@grafana/aws-sdk'; import { rangeUtil, DataSourcePluginOptionsEditorProps, onUpdateDatasourceJsonDataOption, updateDatasourcePluginJsonDataOption, } from '@grafana/data'; -import { ConnectionConfig } from '@grafana/aws-sdk'; - -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { store } from 'app/store/store'; +import { Input, InlineField } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import { createWarningNotification } from 'app/core/copy/appNotification'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { store } from 'app/store/store'; -import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../types'; import { CloudWatchDatasource } from '../datasource'; +import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../types'; + import { XrayLinkConfig } from './XrayLinkConfig'; export type Props = DataSourcePluginOptionsEditorProps; diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.test.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.test.tsx similarity index 87% rename from public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.test.tsx rename to public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.test.tsx index 671cb909f90..3cd88220ee6 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.test.tsx @@ -1,9 +1,10 @@ +import { fireEvent, render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; -import { fireEvent, render, screen, act, within } from '@testing-library/react'; + +import { Dimensions } from '..'; import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { CloudWatchMetricsQuery } from '../../types'; -import userEvent from '@testing-library/user-event'; -import { Dimensions } from '..'; const ds = setupMockedDataSource({ variables: [], @@ -60,7 +61,7 @@ describe('Dimensions', () => { const onChange = jest.fn(); render(); - userEvent.click(screen.getByLabelText('Add')); + await userEvent.click(screen.getByLabelText('Add')); expect(screen.getByTestId('cloudwatch-dimensions-filter-item')).toBeInTheDocument(); expect(onChange).not.toHaveBeenCalled(); }); @@ -74,13 +75,13 @@ describe('Dimensions', () => { ); - userEvent.click(screen.getByLabelText('Add')); + await userEvent.click(screen.getByLabelText('Add')); const filterItemElement = screen.getByTestId('cloudwatch-dimensions-filter-item'); expect(filterItemElement).toBeInTheDocument(); const keyElement = container.querySelector('#cloudwatch-dimensions-filter-item-key'); expect(keyElement).toBeInTheDocument(); - userEvent.type(keyElement!, 'my-key'); + await userEvent.type(keyElement!, 'my-key'); fireEvent.keyDown(keyElement!, { keyCode: 13 }); expect(onChange).not.toHaveBeenCalled(); }); @@ -95,24 +96,20 @@ describe('Dimensions', () => { ); const label = await screen.findByLabelText('Add'); - userEvent.click(label); + await userEvent.click(label); const filterItemElement = screen.getByTestId('cloudwatch-dimensions-filter-item'); expect(filterItemElement).toBeInTheDocument(); const keyElement = container.querySelector('#cloudwatch-dimensions-filter-item-key'); expect(keyElement).toBeInTheDocument(); - await act(async () => { - userEvent.type(keyElement!, 'my-key'); - fireEvent.keyDown(keyElement!, { keyCode: 13 }); - }); + await userEvent.type(keyElement!, 'my-key'); + fireEvent.keyDown(keyElement!, { keyCode: 13 }); expect(onChange).not.toHaveBeenCalled(); const valueElement = container.querySelector('#cloudwatch-dimensions-filter-item-value'); expect(valueElement).toBeInTheDocument(); - await act(async () => { - userEvent.type(valueElement!, 'my-value'); - fireEvent.keyDown(valueElement!, { keyCode: 13 }); - }); + await userEvent.type(valueElement!, 'my-value'); + fireEvent.keyDown(valueElement!, { keyCode: 13 }); expect(onChange).not.toHaveBeenCalledWith({ ...props.query, dimensions: { diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.tsx similarity index 88% rename from public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.tsx rename to public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.tsx index ae645e73d6e..a016b35ae83 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.tsx @@ -1,13 +1,16 @@ import { isEqual } from 'lodash'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { EditorList } from '@grafana/experimental'; + import { CloudWatchDatasource } from '../../datasource'; -import { CloudWatchMetricsQuery, Dimensions as DimensionsType } from '../../types'; +import { Dimensions as DimensionsType, DimensionsQuery } from '../../types'; + import { FilterItem } from './FilterItem'; export interface Props { - query: CloudWatchMetricsQuery; + query: DimensionsQuery; onChange: (dimensions: DimensionsType) => void; datasource: CloudWatchDatasource; dimensionKeys: Array>; @@ -43,8 +46,8 @@ const filterConditionsToDimensions = (filters: DimensionFilterCondition[]) => { }; export const Dimensions: React.FC = ({ query, datasource, dimensionKeys, disableExpressions, onChange }) => { - const dimensionFilters = useMemo(() => dimensionsToFilterConditions(query.dimensions), [query.dimensions]); - const [items, setItems] = useState(dimensionFilters); + const [items, setItems] = useState([]); + useEffect(() => setItems(dimensionsToFilterConditions(query.dimensions)), [query.dimensions]); const onDimensionsChange = (newItems: Array>) => { setItems(newItems); @@ -67,7 +70,7 @@ export const Dimensions: React.FC = ({ query, datasource, dimensionKeys, function makeRenderFilter( datasource: CloudWatchDatasource, - query: CloudWatchMetricsQuery, + query: DimensionsQuery, dimensionKeys: Array>, disableExpressions: boolean ) { diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/FilterItem.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions/FilterItem.tsx similarity index 96% rename from public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/FilterItem.tsx rename to public/app/plugins/datasource/cloudwatch/components/Dimensions/FilterItem.tsx index b9c95a4a6c2..9ffc5847392 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/FilterItem.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Dimensions/FilterItem.tsx @@ -1,16 +1,19 @@ -import React, { FunctionComponent, useMemo } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FunctionComponent, useMemo } from 'react'; import { useAsyncFn } from 'react-use'; + import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; import { InputGroup, AccessoryButton } from '@grafana/experimental'; import { Select, stylesFactory, useTheme2 } from '@grafana/ui'; + import { CloudWatchDatasource } from '../../datasource'; -import { CloudWatchMetricsQuery, Dimensions } from '../../types'; +import { Dimensions, DimensionsQuery } from '../../types'; import { appendTemplateVariables } from '../../utils/utils'; + import { DimensionFilterCondition } from './Dimensions'; export interface Props { - query: CloudWatchMetricsQuery; + query: DimensionsQuery; datasource: CloudWatchDatasource; filter: DimensionFilterCondition; dimensionKeys: Array>; @@ -96,7 +99,7 @@ export const FilterItem: FunctionComponent = ({ } }} /> - +
); diff --git a/public/app/plugins/datasource/cloudwatch/components/Forms.tsx b/public/app/plugins/datasource/cloudwatch/components/Forms.tsx index b7b4d36239e..6afcbf38960 100644 --- a/public/app/plugins/datasource/cloudwatch/components/Forms.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Forms.tsx @@ -1,4 +1,5 @@ import React, { InputHTMLAttributes, FunctionComponent } from 'react'; + import { InlineFormLabel } from '@grafana/ui'; export interface Props extends InputHTMLAttributes { diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx index b5d8261f435..87aa2ae1df7 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsCheatSheet.tsx @@ -1,10 +1,12 @@ -import React, { PureComponent } from 'react'; -import { stripIndent, stripIndents } from 'common-tags'; -import { QueryEditorHelpProps } from '@grafana/data'; -import Prism from 'prismjs'; -import tokenizer from '../syntax'; -import { flattenTokens } from '@grafana/ui/src/slate-plugins/slate-prism'; import { css, cx } from '@emotion/css'; +import { stripIndent, stripIndents } from 'common-tags'; +import Prism from 'prismjs'; +import React, { PureComponent } from 'react'; + +import { QueryEditorHelpProps } from '@grafana/data'; +import { flattenTokens } from '@grafana/ui/src/slate-plugins/slate-prism'; + +import tokenizer from '../syntax'; import { CloudWatchQuery } from '../types'; interface QueryExample { diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx index 8691e3ec8ed..00c24307ef9 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx @@ -1,14 +1,16 @@ // Libraries +import { css } from '@emotion/css'; import React, { memo } from 'react'; // Types import { AbsoluteTimeRange, QueryEditorProps } from '@grafana/data'; import { InlineFormLabel } from '@grafana/ui'; + import { CloudWatchDatasource } from '../datasource'; import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types'; -import { CloudWatchLogsQueryField } from './LogsQueryField'; + import CloudWatchLink from './CloudWatchLink'; -import { css } from '@emotion/css'; +import { CloudWatchLogsQueryField } from './LogsQueryField'; type Props = QueryEditorProps & { allowCustomValue?: boolean; diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx index ef60536b5d5..0aed8959533 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import { CloudWatchLogsQueryField } from './LogsQueryField'; -import { ExploreId } from '../../../../types'; -import { DescribeLogGroupsRequest } from '../types'; -import { SelectableValue } from '@grafana/data'; -// eslint-disable-next-line lodash/import-scope -import _, { DebouncedFunc } from 'lodash'; import { render, screen, fireEvent } from '@testing-library/react'; +import { shallow } from 'enzyme'; +import _, { DebouncedFunc } from 'lodash'; // eslint-disable-line lodash/import-scope +import React from 'react'; import { act } from 'react-dom/test-utils'; + +import { SelectableValue } from '@grafana/data'; + +import { ExploreId } from '../../../../types'; import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; +import { DescribeLogGroupsRequest } from '../types'; + +import { CloudWatchLogsQueryField } from './LogsQueryField'; jest .spyOn(_, 'debounce') diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx index 005e2aed266..050de9a2324 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx @@ -1,7 +1,10 @@ -// Libraries -import React, { ReactNode } from 'react'; +import { css } from '@emotion/css'; import { intersectionBy, debounce, unionBy } from 'lodash'; +import { LanguageMap, languages as prismLanguages } from 'prismjs'; +import React, { ReactNode } from 'react'; +import { Editor, Node, Plugin } from 'slate'; +import { AbsoluteTimeRange, QueryEditorProps, SelectableValue } from '@grafana/data'; import { BracesPlugin, LegacyForms, @@ -11,25 +14,18 @@ import { TypeaheadInput, TypeaheadOutput, } from '@grafana/ui'; - -// Utils & Services -// dom also includes Element polyfills -import { Editor, Node, Plugin } from 'slate'; -import syntax from '../syntax'; - -// Types -import { AbsoluteTimeRange, QueryEditorProps, SelectableValue } from '@grafana/data'; -import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types'; -import { CloudWatchDatasource } from '../datasource'; -import { LanguageMap, languages as prismLanguages } from 'prismjs'; -import { CloudWatchLanguageProvider } from '../language_provider'; -import { css } from '@emotion/css'; -import { ExploreId } from 'app/types'; -import { dispatch } from 'app/store/store'; +import { InputActionMeta } from '@grafana/ui/src/components/Select/types'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification } from 'app/core/copy/appNotification'; -import { InputActionMeta } from '@grafana/ui/src/components/Select/types'; +import { dispatch } from 'app/store/store'; +import { ExploreId } from 'app/types'; + +import { CloudWatchDatasource } from '../datasource'; +import { CloudWatchLanguageProvider } from '../language_provider'; +import syntax from '../syntax'; +import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../types'; import { getStatsGroups } from '../utils/query/getStatsGroups'; + import QueryHeader from './QueryHeader'; export interface CloudWatchLogsQueryFieldProps diff --git a/public/app/plugins/datasource/cloudwatch/components/MathExpressionQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/MathExpressionQueryField.tsx index b8a42a2837d..b9e89f2daa1 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MathExpressionQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MathExpressionQueryField.tsx @@ -1,10 +1,12 @@ -import React, { useCallback, useRef } from 'react'; -import { CodeEditor, Monaco } from '@grafana/ui'; -import language from '../metric-math/definition'; -import { registerLanguage } from '../monarch/register'; import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api'; -import { TRIGGER_SUGGEST } from '../monarch/commands'; +import React, { useCallback, useRef } from 'react'; + +import { CodeEditor, Monaco } from '@grafana/ui'; + import { CloudWatchDatasource } from '../datasource'; +import language from '../metric-math/definition'; +import { TRIGGER_SUGGEST } from '../monarch/commands'; +import { registerLanguage } from '../monarch/register'; export interface Props { onChange: (query: string) => void; diff --git a/public/app/plugins/datasource/cloudwatch/components/MetaInspector.tsx b/public/app/plugins/datasource/cloudwatch/components/MetaInspector.tsx index 6259e651420..923e79c6118 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetaInspector.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetaInspector.tsx @@ -1,8 +1,10 @@ +import { groupBy } from 'lodash'; import React, { useMemo } from 'react'; + import { MetadataInspectorProps } from '@grafana/data'; + import { CloudWatchDatasource } from '../datasource'; import { CloudWatchQuery, CloudWatchJsonData } from '../types'; -import { groupBy } from 'lodash'; export type Props = MetadataInspectorProps; diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx index 4dbf411b2ec..9f6818fbaf4 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.test.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { fireEvent, render, screen, act, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; import selectEvent from 'react-select-event'; + +import { MetricStatEditor } from '..'; import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { CloudWatchMetricsQuery } from '../../types'; -import userEvent from '@testing-library/user-event'; -import { MetricStatEditor } from '..'; const ds = setupMockedDataSource({ variables: [], @@ -47,7 +48,7 @@ describe('MetricStatEditor', () => { const statisticElement = await screen.findByLabelText('Statistic'); expect(statisticElement).toBeInTheDocument(); - userEvent.type(statisticElement, statistic); + await userEvent.type(statisticElement, statistic); fireEvent.keyDown(statisticElement, { keyCode: 13 }); expect(onChange).toHaveBeenCalledWith({ ...props.query, statistic }); expect(onRunQuery).toHaveBeenCalled(); @@ -62,7 +63,7 @@ describe('MetricStatEditor', () => { const statisticElement = await screen.findByLabelText('Statistic'); expect(statisticElement).toBeInTheDocument(); - userEvent.type(statisticElement, statistic); + await userEvent.type(statisticElement, statistic); fireEvent.keyDown(statisticElement, { keyCode: 13 }); expect(onChange).not.toHaveBeenCalled(); expect(onRunQuery).not.toHaveBeenCalled(); diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx index 18260abdf0a..079b53d3010 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/MetricStatEditor.tsx @@ -1,12 +1,14 @@ import React from 'react'; + +import { SelectableValue } from '@grafana/data'; import { EditorField, EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental'; import { Select, Switch } from '@grafana/ui'; + import { Dimensions } from '..'; import { CloudWatchDatasource } from '../../datasource'; import { useDimensionKeys, useMetrics, useNamespaces } from '../../hooks'; import { CloudWatchMetricsQuery } from '../../types'; import { appendTemplateVariables, toOption } from '../../utils/utils'; -import { SelectableValue } from '@grafana/data'; export type Props = { query: CloudWatchMetricsQuery; diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx index f05ee8835ec..73dd9d21e2b 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.test.tsx @@ -1,14 +1,17 @@ -import React from 'react'; -import renderer from 'react-test-renderer'; import { render, screen, act } from '@testing-library/react'; +import React from 'react'; import selectEvent from 'react-select-event'; +import renderer from 'react-test-renderer'; + import { DataSourceInstanceSettings } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; -import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor'; -import { CloudWatchDatasource } from '../datasource'; + import { CustomVariableModel, initialVariableModelState } from '../../../../features/variables/types'; +import { CloudWatchDatasource } from '../datasource'; import { CloudWatchJsonData, MetricEditorMode, MetricQueryType } from '../types'; +import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor'; + const setup = () => { const instanceSettings = { jsonData: { defaultRegion: 'us-east-1' }, diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.tsx index 556d35022ed..38f63c9b58f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor.tsx @@ -1,7 +1,9 @@ import React, { ChangeEvent, PureComponent } from 'react'; + import { QueryEditorProps } from '@grafana/data'; import { EditorField, EditorRow, Space } from '@grafana/experimental'; import { Input } from '@grafana/ui'; + import { CloudWatchDatasource } from '../datasource'; import { isMetricsQuery } from '../guards'; import { @@ -11,9 +13,11 @@ import { MetricEditorMode, MetricQueryType, } from '../types'; -import { Alias, MathExpressionQueryField, MetricStatEditor, SQLBuilderEditor, SQLCodeEditor } from './'; + import QueryHeader from './QueryHeader'; +import { Alias, MathExpressionQueryField, MetricStatEditor, SQLBuilderEditor, SQLCodeEditor } from './'; + export type Props = QueryEditorProps; interface State { diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.test.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.test.tsx index 14a5dfc9961..eaf5d1d616f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; import { act } from 'react-dom/test-utils'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types'; + import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types'; + import MetricsQueryHeader from './MetricsQueryHeader'; const ds = setupMockedDataSource({ diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.tsx b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.tsx index 1523c7ad4b9..29c08845ce5 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/MetricsQueryHeader.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useState } from 'react'; import { SelectableValue } from '@grafana/data'; -import { Button, ConfirmModal, RadioButtonGroup } from '@grafana/ui'; import { InlineSelect, FlexItem } from '@grafana/experimental'; +import { Button, ConfirmModal, RadioButtonGroup } from '@grafana/ui'; import { CloudWatchDatasource } from '../datasource'; import { CloudWatchMetricsQuery, CloudWatchQuery, MetricEditorMode, MetricQueryType } from '../types'; diff --git a/public/app/plugins/datasource/cloudwatch/components/PanelQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/PanelQueryEditor.tsx index 811f342f18d..3c9938b98df 100644 --- a/public/app/plugins/datasource/cloudwatch/components/PanelQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/PanelQueryEditor.tsx @@ -1,9 +1,12 @@ import React, { PureComponent } from 'react'; + import { QueryEditorProps, ExploreMode } from '@grafana/data'; -import { CloudWatchJsonData, CloudWatchQuery } from '../types'; + import { CloudWatchDatasource } from '../datasource'; -import { MetricsQueryEditor } from './MetricsQueryEditor'; +import { CloudWatchJsonData, CloudWatchQuery } from '../types'; + import LogsQueryEditor from './LogsQueryEditor'; +import { MetricsQueryEditor } from './MetricsQueryEditor'; export type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx index 7809e45c614..220d1ad669b 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { render, screen, waitFor, act } from '@testing-library/react'; -import { CloudWatchLogsQuery, CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types'; +import React from 'react'; + import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; +import { CloudWatchLogsQuery, CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types'; + import QueryHeader from './QueryHeader'; const ds = setupMockedDataSource({ diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx index 268abb355e1..ed7b475311f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryHeader.tsx @@ -1,11 +1,13 @@ -import { ExploreMode, SelectableValue } from '@grafana/data'; -import { EditorHeader, InlineSelect } from '@grafana/experimental'; import { pick } from 'lodash'; import React from 'react'; +import { ExploreMode, SelectableValue } from '@grafana/data'; +import { EditorHeader, InlineSelect } from '@grafana/experimental'; + import { CloudWatchDatasource } from '../datasource'; import { useRegions } from '../hooks'; import { CloudWatchQuery, CloudWatchQueryMode } from '../types'; + import MetricsQueryHeader from './MetricsQueryHeader'; interface QueryHeaderProps { diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.test.tsx index 785d4f3925d..24296da0c21 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.test.tsx @@ -1,9 +1,10 @@ -import React from 'react'; -import { SQLBuilderEditor } from '..'; import { render, screen, waitFor } from '@testing-library/react'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; +import React from 'react'; + +import { SQLBuilderEditor } from '..'; import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../expressions'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; const { datasource } = setupMockedDataSource(); diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.tsx index 9b7aaf204e5..174fdbb5a6c 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderEditor.tsx @@ -1,9 +1,12 @@ import React, { useCallback, useEffect, useState } from 'react'; + import { EditorField, EditorRow, EditorRows } from '@grafana/experimental'; import { Input } from '@grafana/ui'; + import SQLGenerator from '../../cloudwatch-sql/SQLGenerator'; import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchMetricsQuery } from '../../types'; + import SQLBuilderSelectRow from './SQLBuilderSelectRow'; import SQLFilter from './SQLFilter'; import SQLGroupBy from './SQLGroupBy'; diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx index 55dac72aa8d..e8948a5be53 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import { selectOptionInTest } from '@grafana/ui'; import { act, render, screen } from '@testing-library/react'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; +import React from 'react'; + +import { selectOptionInTest } from '@grafana/ui'; + import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../expressions'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; + import SQLBuilderSelectRow from './SQLBuilderSelectRow'; const { datasource } = setupMockedDataSource(); diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx index 1c49d73aba0..4caa43aa03d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx @@ -1,12 +1,15 @@ import React, { useEffect, useMemo } from 'react'; + import { SelectableValue, toOption } from '@grafana/data'; import { EditorField, EditorFieldGroup } from '@grafana/experimental'; import { Select, Switch } from '@grafana/ui'; + import { STATISTICS } from '../../cloudwatch-sql/language'; import { CloudWatchDatasource } from '../../datasource'; import { useDimensionKeys, useMetrics, useNamespaces } from '../../hooks'; import { CloudWatchMetricsQuery } from '../../types'; import { appendTemplateVariables } from '../../utils/utils'; + import { getMetricNameFromExpression, getNamespaceFromExpression, diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLFilter.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLFilter.tsx index a87f16155f9..d42f7e98079 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLFilter.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLFilter.tsx @@ -1,14 +1,17 @@ import React, { useMemo, useState } from 'react'; import { useAsyncFn } from 'react-use'; + import { SelectableValue, toOption } from '@grafana/data'; import { AccessoryButton, EditorList, InputGroup } from '@grafana/experimental'; import { Select } from '@grafana/ui'; + import { COMPARISON_OPERATORS, EQUALS } from '../../cloudwatch-sql/language'; import { CloudWatchDatasource } from '../../datasource'; import { QueryEditorExpressionType, QueryEditorOperatorExpression, QueryEditorPropertyType } from '../../expressions'; import { useDimensionKeys } from '../../hooks'; import { CloudWatchMetricsQuery } from '../../types'; import { appendTemplateVariables } from '../../utils/utils'; + import { getFlattenedFilters, getMetricNameFromExpression, diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.test.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.test.tsx index b68113c586f..c3eb94fa6c1 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; import selectEvent from 'react-select-event'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; + import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { createArray, createGroupBy } from '../../__mocks__/sqlUtils'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; + import SQLGroupBy from './SQLGroupBy'; const { datasource } = setupMockedDataSource(); diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.tsx index f7634236e8d..5a1c00d9bb4 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLGroupBy.tsx @@ -1,11 +1,14 @@ import React, { useMemo, useState } from 'react'; + import { SelectableValue, toOption } from '@grafana/data'; import { AccessoryButton, EditorList, InputGroup } from '@grafana/experimental'; import { Select } from '@grafana/ui'; + import { CloudWatchDatasource } from '../../datasource'; import { QueryEditorExpressionType, QueryEditorGroupByExpression, QueryEditorPropertyType } from '../../expressions'; import { useDimensionKeys } from '../../hooks'; import { CloudWatchMetricsQuery } from '../../types'; + import { getFlattenedGroupBys, getMetricNameFromExpression, diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLOrderByGroup.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLOrderByGroup.tsx index d516d68d59e..67d24c6811d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLOrderByGroup.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLOrderByGroup.tsx @@ -1,11 +1,14 @@ import React from 'react'; + import { SelectableValue, toOption } from '@grafana/data'; import { AccessoryButton, EditorField, EditorFieldGroup } from '@grafana/experimental'; import { Select } from '@grafana/ui'; + import { ASC, DESC, STATISTICS } from '../../cloudwatch-sql/language'; import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchMetricsQuery } from '../../types'; import { appendTemplateVariables } from '../../utils/utils'; + import { setOrderBy, setSql } from './utils'; interface SQLBuilderSelectRowProps { diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts index a0b28ca62f4..9c474543fa0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts @@ -1,4 +1,3 @@ -import { SelectableValue } from './../../../../../../../packages/grafana-data/src/types/select'; import { SCHEMA } from '../../cloudwatch-sql/language'; import { QueryEditorExpressionType, @@ -10,6 +9,8 @@ import { } from '../../expressions'; import { SQLExpression, CloudWatchMetricsQuery, Dimensions } from '../../types'; +import { SelectableValue } from './../../../../../../../packages/grafana-data/src/types/select'; + export function getMetricNameFromExpression(selectExpression: SQLExpression['select']): string | undefined { return selectExpression?.parameters?.[0].name; } diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLCodeEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLCodeEditor.tsx index 65bb944ec70..03ca4b998fc 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLCodeEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLCodeEditor.tsx @@ -1,8 +1,10 @@ -import React, { FunctionComponent, useCallback, useEffect } from 'react'; import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api'; +import React, { FunctionComponent, useCallback, useEffect } from 'react'; + import { CodeEditor, Monaco } from '@grafana/ui'; -import { CloudWatchDatasource } from '../datasource'; + import language from '../cloudwatch-sql/definition'; +import { CloudWatchDatasource } from '../datasource'; import { TRIGGER_SUGGEST } from '../monarch/commands'; import { registerLanguage } from '../monarch/register'; diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx index 62d5c7cc65c..fffb71a724a 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx @@ -1,8 +1,10 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import React from 'react'; import { select } from 'react-select-event'; -import { VariableQueryType } from '../../types'; + import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; +import { Dimensions, VariableQueryType } from '../../types'; + import { VariableQueryEditor, Props } from './VariableQueryEditor'; const defaultQuery = { @@ -11,7 +13,6 @@ const defaultQuery = { region: '', metricName: '', dimensionKey: '', - dimensionFilters: '', ec2Filters: '', instanceID: '', attributeName: '', @@ -37,26 +38,42 @@ ds.datasource.getMetrics = jest.fn().mockResolvedValue([ { label: 'i3', value: 'i3' }, { label: 'j3', value: 'j3' }, ]); -ds.datasource.getDimensionKeys = jest.fn().mockImplementation((namespace: string, region: string) => { - if (region === 'a1') { - return Promise.resolve([ - { label: 'q4', value: 'q4' }, - { label: 'r4', value: 'r4' }, - { label: 's4', value: 's4' }, - ]); - } - return Promise.resolve([{ label: 't4', value: 't4' }]); -}); +ds.datasource.getDimensionKeys = jest + .fn() + .mockImplementation((namespace: string, region: string, dimensionFilters?: Dimensions) => { + if (!!dimensionFilters) { + return Promise.resolve([ + { label: 's4', value: 's4' }, + { label: 'v4', value: 'v4' }, + ]); + } + if (region === 'a1') { + return Promise.resolve([ + { label: 'q4', value: 'q4' }, + { label: 'r4', value: 'r4' }, + { label: 's4', value: 's4' }, + ]); + } + return Promise.resolve([{ label: 't4', value: 't4' }]); + }); +ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([ + { label: 'foo', value: 'foo' }, + { label: 'bar', value: 'bar' }, +]); ds.datasource.getVariables = jest.fn().mockReturnValue([]); +const onChange = jest.fn(); const defaultProps: Props = { - onChange: jest.fn(), + onChange: onChange, query: defaultQuery, datasource: ds.datasource, onRunQuery: () => {}, }; describe('VariableEditor', () => { + beforeEach(() => { + onChange.mockClear(); + }); describe('and a new variable is created', () => { it('should trigger a query using the first query type in the array', async () => { const props = defaultProps; @@ -100,6 +117,56 @@ describe('VariableEditor', () => { expect(metricSelect).not.toBeInTheDocument(); }); }); + it('should parse dimensionFilters correctly', async () => { + const props = defaultProps; + props.query = { + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + namespace: 'z2', + region: 'a1', + metricName: 'i3', + dimensionKey: 's4', + dimensionFilters: { s4: 'foo' }, + }; + render(); + + const filterItem = screen.getByTestId('cloudwatch-dimensions-filter-item'); + expect(filterItem).toBeInTheDocument(); + expect(within(filterItem).getByText('s4')).toBeInTheDocument(); + expect(within(filterItem).getByText('foo')).toBeInTheDocument(); + + // change filter key + const keySelect = screen.getByRole('combobox', { name: 'Dimensions filter key' }); + // confirms getDimensionKeys was called with filter and that the element uses keysForDimensionFilter + await select(keySelect, 'v4', { + container: document.body, + }); + expect(ds.datasource.getDimensionKeys).toHaveBeenCalledWith('z2', 'a1', {}, ''); + expect(onChange).toHaveBeenCalledWith({ + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + namespace: 'z2', + region: 'a1', + metricName: 'i3', + dimensionKey: 's4', + dimensionFilters: { v4: undefined }, + }); + + // set filter value + const valueSelect = screen.getByRole('combobox', { name: 'Dimensions filter value' }); + await select(valueSelect, 'bar', { + container: document.body, + }); + expect(onChange).toHaveBeenCalledWith({ + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + namespace: 'z2', + region: 'a1', + metricName: 'i3', + dimensionKey: 's4', + dimensionFilters: { v4: 'bar' }, + }); + }); }); describe('and a different region is selected', () => { it('should clear invalid fields', async () => { @@ -111,6 +178,7 @@ describe('VariableEditor', () => { region: 'a1', metricName: 'i3', dimensionKey: 's4', + dimensionFilters: { s4: 'foo' }, }; render(); @@ -118,7 +186,6 @@ describe('VariableEditor', () => { expect(querySelect).toBeInTheDocument(); expect(screen.queryByText('Dimension Values')).toBeInTheDocument(); const regionSelect = screen.getByRole('combobox', { name: 'Region' }); - regionSelect.click(); await select(regionSelect, 'b1', { container: document.body, }); @@ -133,8 +200,9 @@ describe('VariableEditor', () => { region: 'b1', // metricName i3 exists in the new region and should not be removed metricName: 'i3', - // dimensionKey s4 does not exist in the new region and should be removed + // dimensionKey s4 and valueDimension do not exist in the new region and should be removed dimensionKey: '', + dimensionFilters: {}, }); }); }); diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx index 6a02346cfb7..62aca6bac92 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx @@ -1,12 +1,16 @@ import React from 'react'; import { QueryEditorProps, SelectableValue } from '@grafana/data'; -import { VariableTextField } from './VariableTextField'; +import { InlineField } from '@grafana/ui'; + +import { Dimensions } from '..'; import { CloudWatchDatasource } from '../../datasource'; import { useDimensionKeys, useMetrics, useNamespaces, useRegions } from '../../hooks'; -import { CloudWatchJsonData, CloudWatchQuery, VariableQuery, VariableQueryType } from '../../types'; import { migrateVariableQuery } from '../../migrations'; +import { CloudWatchJsonData, CloudWatchQuery, VariableQuery, VariableQueryType } from '../../types'; + import { VariableQueryField } from './VariableQueryField'; +import { VariableTextField } from './VariableTextField'; export type Props = QueryEditorProps; @@ -25,11 +29,12 @@ const queryTypes: Array<{ value: string; label: string }> = [ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { const parsedQuery = migrateVariableQuery(query); - const { region, namespace, metricName, dimensionKey } = parsedQuery; + const { region, namespace, metricName, dimensionKey, dimensionFilters } = parsedQuery; const [regions, regionIsLoading] = useRegions(datasource); const namespaces = useNamespaces(datasource); const metrics = useMetrics(datasource, region, namespace); const dimensionKeys = useDimensionKeys(datasource, region, namespace, metricName); + const keysForDimensionFilter = useDimensionKeys(datasource, region, namespace, metricName, dimensionFilters ?? {}); const onRegionChange = async (region: string) => { const validatedQuery = await sanitizeQuery({ @@ -48,7 +53,10 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { }; const onQueryChange = (newQuery: VariableQuery) => { - onChange({ ...newQuery, refId: 'CloudWatchVariableQueryEditor-VariableQuery' }); + onChange({ + ...newQuery, + refId: 'CloudWatchVariableQueryEditor-VariableQuery', + }); }; // Reset dimensionValue parameters if namespace or region change @@ -58,7 +66,6 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { await datasource.getMetrics(namespace, region).then((result: Array>) => { if (!result.find((metric) => metric.value === metricName)) { metricName = ''; - dimensionFilters = ''; } }); } @@ -66,7 +73,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { await datasource.getDimensionKeys(namespace, region).then((result: Array>) => { if (!result.find((key) => key.value === dimensionKey)) { dimensionKey = ''; - dimensionFilters = ''; + dimensionFilters = {}; } }); } @@ -86,7 +93,6 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { VariableQueryType.DimensionKeys, VariableQueryType.DimensionValues, ].includes(parsedQuery.queryType); - return ( <> { options={queryTypes} onChange={(value: VariableQueryType) => onQueryChange({ ...parsedQuery, queryType: value })} label="Query Type" + inputId={`variable-query-type-${query.refId}`} /> {hasRegionField && ( { onChange={(value: string) => onRegionChange(value)} label="Region" isLoading={regionIsLoading} + inputId={`variable-query-region-${query.refId}`} /> )} {hasNamespaceField && ( @@ -110,6 +118,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { options={namespaces} onChange={(value: string) => onNamespaceChange(value)} label="Namespace" + inputId={`variable-query-namespace-${query.refId}`} /> )} {parsedQuery.queryType === VariableQueryType.DimensionValues && ( @@ -119,20 +128,26 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { options={metrics} onChange={(value: string) => onQueryChange({ ...parsedQuery, metricName: value })} label="Metric" + inputId={`variable-query-metric-${query.refId}`} /> onQueryChange({ ...parsedQuery, dimensionKey: value })} label="Dimension Key" + inputId={`variable-query-dimension-key-${query.refId}`} /> - onQueryChange({ ...parsedQuery, dimensionFilters: value })} - label="Filters" - /> + + { + onChange({ ...parsedQuery, dimensionFilters: dimensions }); + }} + dimensionKeys={keysForDimensionFilter} + disableExpressions={true} + datasource={datasource} + /> + )} {parsedQuery.queryType === VariableQueryType.EBSVolumeIDs && ( diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx index cb1fa8e82dc..53a32f5db91 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx @@ -1,6 +1,8 @@ +import React from 'react'; + import { SelectableValue } from '@grafana/data'; import { InlineField, Select } from '@grafana/ui'; -import React from 'react'; + import { VariableQueryType } from '../../types'; const LABEL_WIDTH = 20; @@ -10,9 +12,9 @@ interface VariableQueryFieldProps { options: SelectableValue[]; value: T | null; label: string; + inputId?: string; allowCustomValue?: boolean; isLoading?: boolean; - inputId?: string; } export const VariableQueryField = ({ @@ -22,9 +24,10 @@ export const VariableQueryField = ({ options, allowCustomValue = false, isLoading = false, + inputId = label, }: VariableQueryFieldProps) => { return ( - + setLocalValue(e.currentTarget.value)} onBlur={() => onBlur(localValue)} - width={TEXT_WIDTH} /> ); diff --git a/public/app/plugins/datasource/cloudwatch/components/XrayLinkConfig.tsx b/public/app/plugins/datasource/cloudwatch/components/XrayLinkConfig.tsx index 16789f12cb5..9efd3c75164 100644 --- a/public/app/plugins/datasource/cloudwatch/components/XrayLinkConfig.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/XrayLinkConfig.tsx @@ -1,10 +1,10 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { Alert, InlineField, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; +import React from 'react'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { GrafanaTheme2 } from '@grafana/data'; import { DataSourcePicker } from '@grafana/runtime'; +import { Alert, InlineField, useStyles2 } from '@grafana/ui'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; const getStyles = (theme: GrafanaTheme2) => ({ infoText: css` diff --git a/public/app/plugins/datasource/cloudwatch/components/index.ts b/public/app/plugins/datasource/cloudwatch/components/index.ts index 3de2ca07ed2..0362b31b34f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/index.ts +++ b/public/app/plugins/datasource/cloudwatch/components/index.ts @@ -1,4 +1,4 @@ -export { Dimensions } from './MetricStatEditor/Dimensions'; +export { Dimensions } from './Dimensions/Dimensions'; export { QueryInlineField, QueryField } from './Forms'; export { Alias } from './Alias'; export { PanelQueryEditor } from './PanelQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index 5a06bce1476..c4baf75a911 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -1,8 +1,9 @@ -import { ArrayVector, DataFrame, dataFrameToJSON, dateTime, Field, MutableDataFrame } from '@grafana/data'; -import { setDataSourceSrv } from '@grafana/runtime'; import { lastValueFrom, of } from 'rxjs'; import { toArray } from 'rxjs/operators'; +import { ArrayVector, DataFrame, dataFrameToJSON, dateTime, Field, MutableDataFrame } from '@grafana/data'; +import { setDataSourceSrv } from '@grafana/runtime'; + import { labelsVariable, limitVariable, diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index f1ae76f8aef..379ea6e4d45 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -1,3 +1,8 @@ +import { cloneDeep, find, findLast, isEmpty, isString, set } from 'lodash'; +import React from 'react'; +import { from, lastValueFrom, merge, Observable, of, throwError, zip } from 'rxjs'; +import { catchError, concatMap, finalize, map, mergeMap, repeat, scan, share, takeWhile, tap } from 'rxjs/operators'; + import { DataFrame, DataQueryError, @@ -25,10 +30,6 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_sr import { VariableWithMultiSupport } from 'app/features/variables/types'; import { store } from 'app/store/store'; import { AppNotificationTimeout } from 'app/types'; -import { cloneDeep, find, findLast, isEmpty, isString, set } from 'lodash'; -import React from 'react'; -import { from, lastValueFrom, merge, Observable, of, throwError, zip } from 'rxjs'; -import { catchError, concatMap, finalize, map, mergeMap, repeat, scan, share, takeWhile, tap } from 'rxjs/operators'; import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider'; import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage'; @@ -73,6 +74,7 @@ const displayAlert = (datasourceName: string, region: string) => createErrorNotification( `CloudWatch request limit reached in ${region} for data source ${datasourceName}`, '', + undefined, React.createElement(ThrottlingErrorMessage, { region }, null) ) ) @@ -123,7 +125,7 @@ export class CloudWatchDatasource this.logsTimeout = instanceSettings.jsonData.logsTimeout || '15m'; this.sqlCompletionItemProvider = new SQLCompletionItemProvider(this, this.templateSrv); this.metricMathCompletionItemProvider = new MetricMathCompletionItemProvider(this, this.templateSrv); - this.variables = new CloudWatchVariableSupport(this); + this.variables = new CloudWatchVariableSupport(this, this.templateSrv); } query(options: DataQueryRequest): Observable { diff --git a/public/app/plugins/datasource/cloudwatch/hooks.ts b/public/app/plugins/datasource/cloudwatch/hooks.ts index ee8a8ea63ab..0ee791f7dfa 100644 --- a/public/app/plugins/datasource/cloudwatch/hooks.ts +++ b/public/app/plugins/datasource/cloudwatch/hooks.ts @@ -1,10 +1,12 @@ import { useEffect, useState } from 'react'; -import { SelectableValue, toOption } from '@grafana/data'; -import { appendTemplateVariables } from './utils/utils'; -import { Dimensions } from './types'; -import { CloudWatchDatasource } from './datasource'; import { useDeepCompareEffect } from 'react-use'; +import { SelectableValue, toOption } from '@grafana/data'; + +import { CloudWatchDatasource } from './datasource'; +import { Dimensions } from './types'; +import { appendTemplateVariables } from './utils/utils'; + export const useRegions = (datasource: CloudWatchDatasource): [Array>, boolean] => { const [regionsIsLoading, setRegionsIsLoading] = useState(false); const [regions, setRegions] = useState>>([{ label: 'default', value: 'default' }]); diff --git a/public/app/plugins/datasource/cloudwatch/language_provider.test.ts b/public/app/plugins/datasource/cloudwatch/language_provider.test.ts index 38743252743..f69ad66c303 100644 --- a/public/app/plugins/datasource/cloudwatch/language_provider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language_provider.test.ts @@ -1,9 +1,10 @@ -import { Value } from 'slate'; -import { TypeaheadOutput } from '@grafana/ui'; -import { CloudWatchDatasource } from './datasource'; -import { GetLogGroupFieldsResponse } from './types'; -import { CloudWatchLanguageProvider } from './language_provider'; import Prism, { Token } from 'prismjs'; +import { Value } from 'slate'; + +import { TypeaheadOutput } from '@grafana/ui'; + +import { CloudWatchDatasource } from './datasource'; +import { CloudWatchLanguageProvider } from './language_provider'; import { AGGREGATION_FUNCTIONS_STATS, BOOLEAN_FUNCTIONS, @@ -14,6 +15,7 @@ import { STRING_FUNCTIONS, FIELD_AND_FILTER_FUNCTIONS, } from './syntax'; +import { GetLogGroupFieldsResponse } from './types'; const fields = ['field1', '@message']; diff --git a/public/app/plugins/datasource/cloudwatch/language_provider.ts b/public/app/plugins/datasource/cloudwatch/language_provider.ts index 24ec295638c..75848971262 100644 --- a/public/app/plugins/datasource/cloudwatch/language_provider.ts +++ b/public/app/plugins/datasource/cloudwatch/language_provider.ts @@ -1,9 +1,11 @@ import { sortedUniq } from 'lodash'; -import { lastValueFrom } from 'rxjs'; import Prism, { Grammar } from 'prismjs'; +import { lastValueFrom } from 'rxjs'; + import { AbsoluteTimeRange, HistoryItem, LanguageProvider } from '@grafana/data'; import { CompletionItemGroup, SearchFunctionType, Token, TypeaheadInput, TypeaheadOutput } from '@grafana/ui'; +import { CloudWatchDatasource } from './datasource'; import syntax, { AGGREGATION_FUNCTIONS_STATS, BOOLEAN_FUNCTIONS, @@ -15,7 +17,6 @@ import syntax, { STRING_FUNCTIONS, } from './syntax'; import { CloudWatchQuery, TSDBResponse } from './types'; -import { CloudWatchDatasource } from './datasource'; export type CloudWatchHistoryItem = HistoryItem; diff --git a/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.test.ts index 9495b07c24d..19c8873cb70 100644 --- a/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.test.ts @@ -1,10 +1,11 @@ +import { getTemplateSrv } from '@grafana/runtime'; +import { Monaco, monacoTypes } from '@grafana/ui'; + +import * as MetricMathTestData from '../../__mocks__/metric-math-test-data'; import MonacoMock from '../../__mocks__/monarch/Monaco'; import TextModel from '../../__mocks__/monarch/TextModel'; -import { MetricMathCompletionItemProvider } from './CompletionItemProvider'; -import { getTemplateSrv } from '@grafana/runtime'; import { CloudWatchDatasource } from '../../datasource'; import cloudWatchMetricMathLanguageDefinition from '../definition'; -import { Monaco, monacoTypes } from '@grafana/ui'; import { METRIC_MATH_FNS, METRIC_MATH_KEYWORDS, @@ -12,7 +13,8 @@ import { METRIC_MATH_PERIODS, METRIC_MATH_STATISTIC_KEYWORD_STRINGS, } from '../language'; -import * as MetricMathTestData from '../../__mocks__/metric-math-test-data'; + +import { MetricMathCompletionItemProvider } from './CompletionItemProvider'; const getSuggestions = async (value: string, position: monacoTypes.IPosition) => { const setup = new MetricMathCompletionItemProvider( diff --git a/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.ts b/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.ts index 20b86ff0f16..f8feb524b8f 100644 --- a/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.ts @@ -1,7 +1,11 @@ +import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import type { Monaco, monacoTypes } from '@grafana/ui'; + +import { CloudWatchDatasource } from '../../datasource'; +import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; +import { LinkedToken } from '../../monarch/LinkedToken'; import { TRIGGER_SUGGEST } from '../../monarch/commands'; import { SuggestionKind, CompletionItemPriority, StatementPosition } from '../../monarch/types'; -import { LinkedToken } from '../../monarch/LinkedToken'; import { METRIC_MATH_FNS, METRIC_MATH_KEYWORDS, @@ -9,12 +13,10 @@ import { METRIC_MATH_PERIODS, METRIC_MATH_STATISTIC_KEYWORD_STRINGS, } from '../language'; -import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; -import { MetricMathTokenTypes } from './types'; -import { CloudWatchDatasource } from '../../datasource'; -import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; + import { getStatementPosition } from './statementPosition'; import { getSuggestionKinds } from './suggestionKind'; +import { MetricMathTokenTypes } from './types'; type CompletionItem = monacoTypes.languages.CompletionItem; diff --git a/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.test.ts index a4b32712015..ab7aa911e86 100644 --- a/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.test.ts @@ -1,11 +1,13 @@ import { monacoTypes } from '@grafana/ui'; + +import * as MetricMathTestQueries from '../../__mocks__/metric-math-test-data'; import MonacoMock from '../../__mocks__/monarch/Monaco'; import TextModel from '../../__mocks__/monarch/TextModel'; -import * as MetricMathTestQueries from '../../__mocks__/metric-math-test-data'; import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; import { StatementPosition } from '../../monarch/types'; -import { getStatementPosition } from './statementPosition'; import cloudWatchSqlLanguageDefinition from '../definition'; + +import { getStatementPosition } from './statementPosition'; import { MetricMathTokenTypes } from './types'; describe('statementPosition', () => { diff --git a/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.ts b/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.ts index 2e86bb446e4..099186363e8 100644 --- a/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.ts +++ b/public/app/plugins/datasource/cloudwatch/metric-math/completion/statementPosition.ts @@ -1,5 +1,6 @@ import { LinkedToken } from '../../monarch/LinkedToken'; import { StatementPosition } from '../../monarch/types'; + import { MetricMathTokenTypes } from './types'; export function getStatementPosition(currentToken: LinkedToken | null): StatementPosition { diff --git a/public/app/plugins/datasource/cloudwatch/migration.test.ts b/public/app/plugins/datasource/cloudwatch/migration.test.ts index 4c56fd06946..4745efa9151 100644 --- a/public/app/plugins/datasource/cloudwatch/migration.test.ts +++ b/public/app/plugins/datasource/cloudwatch/migration.test.ts @@ -1,4 +1,5 @@ import { DataQuery } from '@grafana/data'; + import { migrateMultipleStatsAnnotationQuery, migrateMultipleStatsMetricsQuery, @@ -206,7 +207,7 @@ describe('migration', () => { expect(query.namespace).toBe('AWS/RDS'); expect(query.metricName).toBe('CPUUtilization'); expect(query.dimensionKey).toBe('DBInstanceIdentifier'); - expect(query.dimensionFilters).toBe(''); + expect(query.dimensionFilters).toStrictEqual({}); }); }); describe('and filter param is defined by user', () => { @@ -219,9 +220,18 @@ describe('migration', () => { expect(query.namespace).toBe('AWS/RDS'); expect(query.metricName).toBe('CPUUtilization'); expect(query.dimensionKey).toBe('DBInstanceIdentifier'); - expect(query.dimensionFilters).toBe('{"InstanceId":"$instance_id"}'); + expect(query.dimensionFilters).toStrictEqual({ InstanceId: '$instance_id' }); }); }); }); }); + describe('when resource_arns query is used', () => { + it('should parse the query', () => { + const query = migrateVariableQuery('resource_arns(us-east-1,rds:db,{"environment":["$environment"]})'); + expect(query.queryType).toBe(VariableQueryType.ResourceArns); + expect(query.region).toBe('us-east-1'); + expect(query.resourceType).toBe('rds:db'); + expect(query.tags).toBe('{"environment":["$environment"]}'); + }); + }); }); diff --git a/public/app/plugins/datasource/cloudwatch/migrations.ts b/public/app/plugins/datasource/cloudwatch/migrations.ts index 8bc3387e28d..d03cd7661ec 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations.ts @@ -1,5 +1,6 @@ import { AnnotationQuery, DataQuery } from '@grafana/data'; import { getNextRefIdChar } from 'app/core/utils/query'; + import { MetricEditorMode, CloudWatchAnnotationQuery, @@ -80,7 +81,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable region: '', metricName: '', dimensionKey: '', - dimensionFilters: '', + dimensionFilters: {}, ec2Filters: '', instanceID: '', attributeName: '', @@ -122,7 +123,14 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable newQuery.namespace = dimensionValuesQuery[2]; newQuery.metricName = dimensionValuesQuery[3]; newQuery.dimensionKey = dimensionValuesQuery[4]; - newQuery.dimensionFilters = dimensionValuesQuery[6] || ''; + newQuery.dimensionFilters = {}; + if (!!dimensionValuesQuery[6]) { + try { + newQuery.dimensionFilters = JSON.parse(dimensionValuesQuery[6]); + } catch { + throw new Error(`unable to migrate poorly formed filters: ${dimensionValuesQuery[6]}`); + } + } return newQuery; } @@ -148,7 +156,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable newQuery.queryType = VariableQueryType.ResourceArns; newQuery.region = resourceARNsQuery[1]; newQuery.resourceType = resourceARNsQuery[2]; - newQuery.tags = JSON.parse(resourceARNsQuery[3]) || ''; + newQuery.tags = resourceARNsQuery[3] || ''; return newQuery; } diff --git a/public/app/plugins/datasource/cloudwatch/module.tsx b/public/app/plugins/datasource/cloudwatch/module.tsx index 4309c2e307f..2cd9a287e5f 100644 --- a/public/app/plugins/datasource/cloudwatch/module.tsx +++ b/public/app/plugins/datasource/cloudwatch/module.tsx @@ -1,12 +1,13 @@ import { DataSourcePlugin } from '@grafana/data'; -import { ConfigEditor } from './components/ConfigEditor'; -import { CloudWatchDatasource } from './datasource'; + import { CloudWatchAnnotationsQueryCtrl } from './annotations_query_ctrl'; -import { CloudWatchJsonData, CloudWatchQuery } from './types'; -import { CloudWatchLogsQueryEditor } from './components/LogsQueryEditor'; -import { PanelQueryEditor } from './components/PanelQueryEditor'; -import { MetaInspector } from './components/MetaInspector'; +import { ConfigEditor } from './components/ConfigEditor'; import LogsCheatSheet from './components/LogsCheatSheet'; +import { CloudWatchLogsQueryEditor } from './components/LogsQueryEditor'; +import { MetaInspector } from './components/MetaInspector'; +import { PanelQueryEditor } from './components/PanelQueryEditor'; +import { CloudWatchDatasource } from './datasource'; +import { CloudWatchJsonData, CloudWatchQuery } from './types'; export const plugin = new DataSourcePlugin( CloudWatchDatasource diff --git a/public/app/plugins/datasource/cloudwatch/monarch/CompletionItemProvider.ts b/public/app/plugins/datasource/cloudwatch/monarch/CompletionItemProvider.ts index 06404442925..cf2d1dfed30 100644 --- a/public/app/plugins/datasource/cloudwatch/monarch/CompletionItemProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/monarch/CompletionItemProvider.ts @@ -1,9 +1,10 @@ -import type { Monaco, monacoTypes } from '@grafana/ui'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; +import type { Monaco, monacoTypes } from '@grafana/ui'; + import { CloudWatchDatasource } from '../datasource'; -import { linkedTokenBuilder } from './linkedTokenBuilder'; import { LinkedToken } from './LinkedToken'; +import { linkedTokenBuilder } from './linkedTokenBuilder'; import { LanguageDefinition } from './register'; import { StatementPosition, SuggestionKind, TokenTypes } from './types'; diff --git a/public/app/plugins/datasource/cloudwatch/monarch/LinkedToken.ts b/public/app/plugins/datasource/cloudwatch/monarch/LinkedToken.ts index 227057efecf..86e8a11168c 100644 --- a/public/app/plugins/datasource/cloudwatch/monarch/LinkedToken.ts +++ b/public/app/plugins/datasource/cloudwatch/monarch/LinkedToken.ts @@ -1,4 +1,5 @@ import { monacoTypes } from '@grafana/ui'; + import { TokenTypes } from './types'; export class LinkedToken { diff --git a/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.test.ts b/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.test.ts index 89907a66e85..3025302bb05 100644 --- a/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.test.ts +++ b/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.test.ts @@ -1,11 +1,13 @@ import { monacoTypes } from '@grafana/ui'; + +import { multiLineFullQuery, singleLineFullQuery } from '../__mocks__/cloudwatch-sql-test-data'; import MonacoMock from '../__mocks__/monarch/Monaco'; import TextModel from '../__mocks__/monarch/TextModel'; -import { multiLineFullQuery, singleLineFullQuery } from '../__mocks__/cloudwatch-sql-test-data'; -import { linkedTokenBuilder } from './linkedTokenBuilder'; -import { DESC, SELECT } from '../cloudwatch-sql/language'; -import cloudWatchSqlLanguageDefinition from '../cloudwatch-sql/definition'; import { SQLTokenTypes } from '../cloudwatch-sql/completion/types'; +import cloudWatchSqlLanguageDefinition from '../cloudwatch-sql/definition'; +import { DESC, SELECT } from '../cloudwatch-sql/language'; + +import { linkedTokenBuilder } from './linkedTokenBuilder'; describe('linkedTokenBuilder', () => { describe('singleLineFullQuery', () => { diff --git a/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.ts b/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.ts index f43002fcb1f..e468752c426 100644 --- a/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.ts +++ b/public/app/plugins/datasource/cloudwatch/monarch/linkedTokenBuilder.ts @@ -1,8 +1,8 @@ import type { monacoTypes } from '@grafana/ui'; import { LinkedToken } from './LinkedToken'; -import { Monaco, TokenTypes } from './types'; import { LanguageDefinition } from './register'; +import { Monaco, TokenTypes } from './types'; export function linkedTokenBuilder( monaco: Monaco, diff --git a/public/app/plugins/datasource/cloudwatch/monarch/register.ts b/public/app/plugins/datasource/cloudwatch/monarch/register.ts index 2a69e7156ae..3a3f7b4e2d6 100644 --- a/public/app/plugins/datasource/cloudwatch/monarch/register.ts +++ b/public/app/plugins/datasource/cloudwatch/monarch/register.ts @@ -1,7 +1,9 @@ -import { Monaco } from '@grafana/ui'; -import { CompletionItemProvider } from './CompletionItemProvider'; import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api'; +import { Monaco } from '@grafana/ui'; + +import { CompletionItemProvider } from './CompletionItemProvider'; + export type LanguageDefinition = { id: string; extensions: string[]; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts index 6e91e209b2a..72dfae2fe70 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource.test.ts @@ -1,4 +1,7 @@ import { interval, lastValueFrom, of, throwError } from 'rxjs'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; +import { getTemplateSrvDependencies } from 'test/helpers/getTemplateSrvDependencies'; + import { DataFrame, DataQueryErrorType, @@ -6,10 +9,14 @@ import { dateMath, getFrameDisplayName, } from '@grafana/data'; - -import * as redux from 'app/store/store'; -import { CloudWatchDatasource } from '../datasource'; +import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { TemplateSrv } from 'app/features/templating/template_srv'; +import * as redux from 'app/store/store'; + +import { convertToStoreState } from '../../../../../test/helpers/convertToStoreState'; +import { CustomVariableModel, initialVariableModelState, VariableHide } from '../../../../features/variables/types'; +import { CloudWatchDatasource } from '../datasource'; import { CloudWatchJsonData, CloudWatchLogsQuery, @@ -19,14 +26,7 @@ import { MetricEditorMode, MetricQueryType, } from '../types'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ -import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { convertToStoreState } from '../../../../../test/helpers/convertToStoreState'; -import { getTemplateSrvDependencies } from 'test/helpers/getTemplateSrvDependencies'; -import { CustomVariableModel, initialVariableModelState, VariableHide } from '../../../../features/variables/types'; - import * as rxjsUtils from '../utils/rxjs/increasingInterval'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/plugins/datasource/cloudwatch/syntax.ts b/public/app/plugins/datasource/cloudwatch/syntax.ts index 92c279ec527..742db4e4908 100644 --- a/public/app/plugins/datasource/cloudwatch/syntax.ts +++ b/public/app/plugins/datasource/cloudwatch/syntax.ts @@ -1,4 +1,5 @@ import { Grammar } from 'prismjs'; + import { CompletionItem } from '@grafana/ui'; export const QUERY_COMMANDS: CompletionItem[] = [ diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 472d400b55b..0380c8a1d78 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -1,5 +1,5 @@ -import { DataQuery, DataSourceRef, SelectableValue } from '@grafana/data'; import { AwsAuthDataSourceSecureJsonData, AwsAuthDataSourceJsonData } from '@grafana/aws-sdk'; +import { DataQuery, DataSourceRef, SelectableValue } from '@grafana/data'; export interface Dimensions { [key: string]: string | string[]; @@ -35,6 +35,13 @@ export interface SQLExpression { limit?: number; } +export interface DimensionsQuery extends DataQuery { + namespace: string; + region: string; + metricName?: string; + dimensions?: Dimensions; +} + export interface CloudWatchMetricsQuery extends DataQuery { queryMode?: 'Metrics'; metricQueryType?: MetricQueryType; @@ -386,7 +393,7 @@ export interface VariableQuery extends DataQuery { region: string; metricName: string; dimensionKey: string; - dimensionFilters: string; + dimensionFilters?: Dimensions; ec2Filters: string; instanceID: string; attributeName: string; diff --git a/public/app/plugins/datasource/cloudwatch/utils/datalinks.test.ts b/public/app/plugins/datasource/cloudwatch/utils/datalinks.test.ts index c8ccb1ba9fa..25edfd33b16 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/datalinks.test.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/datalinks.test.ts @@ -1,7 +1,8 @@ import { DataQueryResponse, dateMath } from '@grafana/data'; -import { addDataLinksToLogsResponse } from './datalinks'; import { setDataSourceSrv } from '@grafana/runtime'; +import { addDataLinksToLogsResponse } from './datalinks'; + describe('addDataLinksToLogsResponse', () => { it('should add data links to response', async () => { const mockResponse: DataQueryResponse = { diff --git a/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts b/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts index 897c4441d60..05f19e61925 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts @@ -1,8 +1,9 @@ import { DataFrame, DataLink, DataQueryRequest, DataQueryResponse, ScopedVars, TimeRange } from '@grafana/data'; -import { CloudWatchLogsQuery, CloudWatchQuery } from '../types'; -import { AwsUrl, encodeUrl } from '../aws_url'; import { getDataSourceSrv } from '@grafana/runtime'; +import { AwsUrl, encodeUrl } from '../aws_url'; +import { CloudWatchLogsQuery, CloudWatchQuery } from '../types'; + type ReplaceFn = ( target?: string, scopedVars?: ScopedVars, diff --git a/public/app/plugins/datasource/cloudwatch/utils/logsRetry.test.ts b/public/app/plugins/datasource/cloudwatch/utils/logsRetry.test.ts index d6103230c12..fd4c854bd55 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/logsRetry.test.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/logsRetry.test.ts @@ -1,10 +1,13 @@ -import { runWithRetry } from './logsRetry'; -import { toArray } from 'rxjs/operators'; import { lastValueFrom, of, throwError } from 'rxjs'; +import { toArray } from 'rxjs/operators'; + import { dataFrameToJSON, MutableDataFrame } from '@grafana/data'; import { DataResponse, FetchError } from '@grafana/runtime'; + import { StartQueryRequest } from '../types'; +import { runWithRetry } from './logsRetry'; + describe('runWithRetry', () => { const timeoutPass = () => false; const timeoutFail = () => true; diff --git a/public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts b/public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts index 06db908daa2..9ff34b37737 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/logsRetry.ts @@ -1,7 +1,9 @@ import { Observable, Subscription } from 'rxjs'; -import { FetchError, toDataQueryResponse } from '@grafana/runtime'; -import { StartQueryRequest } from '../types'; + import { DataFrame, DataFrameJSON, DataQueryError } from '@grafana/data'; +import { FetchError, toDataQueryResponse } from '@grafana/runtime'; + +import { StartQueryRequest } from '../types'; type Result = { frames: DataFrameJSON[]; error?: string }; diff --git a/public/app/plugins/datasource/cloudwatch/utils/utils.ts b/public/app/plugins/datasource/cloudwatch/utils/utils.ts index 9ca92516fcf..d0f626cb2a1 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/utils.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/utils.ts @@ -1,4 +1,5 @@ import { SelectableValue } from '@grafana/data'; + import { CloudWatchDatasource } from './../datasource'; export const toOption = (value: string) => ({ label: value, value }); diff --git a/public/app/plugins/datasource/cloudwatch/variables.test.ts b/public/app/plugins/datasource/cloudwatch/variables.test.ts new file mode 100644 index 00000000000..a6e48356153 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/variables.test.ts @@ -0,0 +1,172 @@ +import { dimensionVariable, labelsVariable, setupMockedDataSource } from './__mocks__/CloudWatchDataSource'; +import { VariableQuery, VariableQueryType } from './types'; +import { CloudWatchVariableSupport } from './variables'; + +const defaultQuery: VariableQuery = { + queryType: VariableQueryType.Regions, + namespace: 'foo', + region: 'bar', + metricName: '', + dimensionKey: '', + ec2Filters: '', + instanceID: '', + attributeName: '', + resourceType: '', + tags: '', + refId: '', +}; + +const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable] }); +ds.datasource.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a' }]); +ds.datasource.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]); +ds.datasource.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]); +ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]); +const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]); +const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]); +const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]); +const getResourceARNs = jest.fn().mockResolvedValue([{ label: 'h', value: 'h' }]); + +const variables = new CloudWatchVariableSupport(ds.datasource, ds.templateService); + +describe('variables', () => { + it('should run regions', async () => { + const result = await variables.execute({ ...defaultQuery }); + expect(result).toEqual([{ text: 'a', value: 'a', expandable: true }]); + }); + + it('should run namespaces', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Namespaces }); + expect(result).toEqual([{ text: 'b', value: 'b', expandable: true }]); + }); + + it('should run metrics', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Metrics }); + expect(result).toEqual([{ text: 'c', value: 'c', expandable: true }]); + }); + + it('should run dimension keys', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.DimensionKeys }); + expect(result).toEqual([{ text: 'd', value: 'd', expandable: true }]); + }); + + describe('dimension values', () => { + const query = { + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + metricName: 'abc', + dimensionKey: 'efg', + dimensionFilters: { a: 'b' }, + }; + beforeEach(() => { + ds.datasource.getDimensionValues = getDimensionValues; + getDimensionValues.mockClear(); + }); + + it('should not run if dimension key not set', async () => { + const result = await variables.execute({ ...query, dimensionKey: '' }); + expect(getDimensionValues).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should not run if metric name not set', async () => { + const result = await variables.execute({ ...query, metricName: '' }); + expect(getDimensionValues).not.toBeCalled(); + expect(result).toEqual([]); + }); + it('should run if values are set', async () => { + const result = await variables.execute(query); + expect(getDimensionValues).toBeCalledWith( + query.region, + query.namespace, + query.metricName, + query.dimensionKey, + query.dimensionFilters + ); + expect(result).toEqual([{ text: 'e', value: 'e', expandable: true }]); + }); + }); + + describe('EBS volume ids', () => { + beforeEach(() => { + ds.datasource.getEbsVolumeIds = getEbsVolumeIds; + getEbsVolumeIds.mockClear(); + }); + + it('should not run if instance id not set', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.EBSVolumeIDs }); + expect(getEbsVolumeIds).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should run if instance id set', async () => { + const result = await variables.execute({ + ...defaultQuery, + queryType: VariableQueryType.EBSVolumeIDs, + instanceID: 'foo', + }); + expect(getEbsVolumeIds).toBeCalledWith(defaultQuery.region, 'foo'); + expect(result).toEqual([{ text: 'f', value: 'f', expandable: true }]); + }); + }); + + describe('EC2 instance attributes', () => { + const query = { + ...defaultQuery, + queryType: VariableQueryType.EC2InstanceAttributes, + attributeName: 'abc', + ec2Filters: '{"$dimension":["b"]}', + }; + beforeEach(() => { + ds.datasource.getEc2InstanceAttribute = getEc2InstanceAttribute; + getEc2InstanceAttribute.mockClear(); + }); + + it('should not run if instance id not set', async () => { + const result = await variables.execute({ ...query, attributeName: '' }); + expect(getEc2InstanceAttribute).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should run if instance id set', async () => { + const result = await variables.execute(query); + expect(getEc2InstanceAttribute).toBeCalledWith(query.region, query.attributeName, { env: ['b'] }); + expect(result).toEqual([{ text: 'g', value: 'g', expandable: true }]); + }); + }); + + describe('resource arns', () => { + const query = { + ...defaultQuery, + queryType: VariableQueryType.ResourceArns, + resourceType: 'abc', + tags: '{"a":${labels:json}}', + }; + beforeEach(() => { + ds.datasource.getResourceARNs = getResourceARNs; + getResourceARNs.mockClear(); + }); + + it('should not run if instance id not set', async () => { + const result = await variables.execute({ ...query, resourceType: '' }); + expect(getResourceARNs).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should run if instance id set', async () => { + const result = await variables.execute(query); + expect(getResourceARNs).toBeCalledWith(query.region, query.resourceType, { a: ['InstanceId', 'InstanceType'] }); + expect(result).toEqual([{ text: 'h', value: 'h', expandable: true }]); + }); + }); + + it('should run statistics', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Statistics }); + expect(result).toEqual([ + { text: 'Average', value: 'Average', expandable: true }, + { text: 'Maximum', value: 'Maximum', expandable: true }, + { text: 'Minimum', value: 'Minimum', expandable: true }, + { text: 'Sum', value: 'Sum', expandable: true }, + { text: 'SampleCount', value: 'SampleCount', expandable: true }, + ]); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/variables.ts b/public/app/plugins/datasource/cloudwatch/variables.ts index 1443df38ce3..a4eb2ea5397 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.ts @@ -1,18 +1,22 @@ import { from, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@grafana/data'; -import { CloudWatchDatasource } from './datasource'; -import { VariableQuery, VariableQueryType } from './types'; -import { migrateVariableQuery } from './migrations'; +import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@grafana/data'; +import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; + import { VariableQueryEditor } from './components/VariableQueryEditor/VariableQueryEditor'; +import { CloudWatchDatasource } from './datasource'; +import { migrateVariableQuery } from './migrations'; +import { VariableQuery, VariableQueryType } from './types'; export class CloudWatchVariableSupport extends CustomVariableSupport { private readonly datasource: CloudWatchDatasource; + private readonly templateSrv: TemplateSrv; - constructor(datasource: CloudWatchDatasource) { + constructor(datasource: CloudWatchDatasource, templateSrv: TemplateSrv = getTemplateSrv()) { super(); this.datasource = datasource; + this.templateSrv = templateSrv; this.query = this.query.bind(this); } @@ -91,11 +95,13 @@ export class CloudWatchVariableSupport extends CustomVariableSupport ({ text: s.label, value: s.value, @@ -119,9 +125,9 @@ export class CloudWatchVariableSupport extends CustomVariableSupport ({ @@ -135,9 +141,9 @@ export class CloudWatchVariableSupport extends CustomVariableSupport ({ diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx index afef996aff0..0a98f2bd947 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.test.tsx @@ -1,13 +1,15 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { getDefaultTimeRange, LoadingState } from '@grafana/data'; -import { SHARED_DASHBOARD_QUERY } from './types'; -import { DashboardQueryEditor } from './DashboardQueryEditor'; -import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { DashboardModel } from 'app/features/dashboard/state'; import { setDataSourceSrv } from '@grafana/runtime'; import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; +import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { DashboardModel } from 'app/features/dashboard/state'; + +import { DashboardQueryEditor } from './DashboardQueryEditor'; +import { SHARED_DASHBOARD_QUERY } from './types'; jest.mock('app/core/config', () => ({ ...(jest.requireActual('app/core/config') as unknown as object), @@ -79,7 +81,7 @@ describe('DashboardQueryEditor', () => { ); const select = screen.getByText('Choose panel'); - userEvent.click(select); + await userEvent.click(select); const myFirstPanel = await screen.findByText('My first panel'); expect(myFirstPanel).toBeInTheDocument(); @@ -102,7 +104,7 @@ describe('DashboardQueryEditor', () => { ); const select = screen.getByText('Choose panel'); - userEvent.click(select); + await userEvent.click(select); expect(screen.queryByText('My first panel')).not.toBeInTheDocument(); diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx index 6ce8d461fb7..6a7cc23a35f 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx @@ -1,19 +1,18 @@ -import React, { useCallback, useMemo } from 'react'; import { css } from '@emotion/css'; -import pluralize from 'pluralize'; import { useId } from '@react-aria/utils'; +import pluralize from 'pluralize'; +import React, { useCallback, useMemo } from 'react'; import { useAsync } from 'react-use'; -import { InlineField, Select, useStyles2, VerticalGroup } from '@grafana/ui'; import { DataQuery, GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data'; - +import { InlineField, Select, useStyles2, VerticalGroup } from '@grafana/ui'; import config from 'app/core/config'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { PanelModel } from 'app/features/dashboard/state'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { PanelModel } from 'app/features/dashboard/state'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { filterPanelDataToQuery } from 'app/features/query/components/QueryEditorRow'; -import { DashboardQueryRow } from './DashboardQueryRow'; +import { DashboardQueryRow } from './DashboardQueryRow'; import { DashboardQuery, ResultInfo, SHARED_DASHBOARD_QUERY } from './types'; function getQueryDisplayText(query: DataQuery): string { diff --git a/public/app/plugins/datasource/dashboard/DashboardQueryRow.tsx b/public/app/plugins/datasource/dashboard/DashboardQueryRow.tsx index 5b3d957f170..426798625c9 100644 --- a/public/app/plugins/datasource/dashboard/DashboardQueryRow.tsx +++ b/public/app/plugins/datasource/dashboard/DashboardQueryRow.tsx @@ -1,7 +1,8 @@ -import React, { ReactElement } from 'react'; import { css } from '@emotion/css'; -import { Icon, useStyles } from '@grafana/ui'; +import React, { ReactElement } from 'react'; + import { GrafanaTheme } from '@grafana/data'; +import { Icon, useStyles } from '@grafana/ui'; import { ResultInfo } from './types'; diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index 0f7f8d8d3f3..e4d9465c04c 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -1,4 +1,5 @@ import { DataSourceApi, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data'; + import { DashboardQuery } from './types'; /** diff --git a/public/app/plugins/datasource/dashboard/module.ts b/public/app/plugins/datasource/dashboard/module.ts index ac32bca87e3..1fa8553046e 100644 --- a/public/app/plugins/datasource/dashboard/module.ts +++ b/public/app/plugins/datasource/dashboard/module.ts @@ -1,4 +1,5 @@ -import { DashboardDatasource } from './datasource'; import { DataSourcePlugin } from '@grafana/data'; +import { DashboardDatasource } from './datasource'; + export const plugin = new DataSourcePlugin(DashboardDatasource); diff --git a/public/app/plugins/datasource/dashboard/runSharedRequest.test.ts b/public/app/plugins/datasource/dashboard/runSharedRequest.test.ts index ad6c4a6dd52..956c9bea66f 100644 --- a/public/app/plugins/datasource/dashboard/runSharedRequest.test.ts +++ b/public/app/plugins/datasource/dashboard/runSharedRequest.test.ts @@ -1,6 +1,7 @@ -import { isSharedDashboardQuery } from './runSharedRequest'; import { DataSourceApi } from '@grafana/data'; +import { isSharedDashboardQuery } from './runSharedRequest'; + describe('SharedQueryRunner', () => { it('should identify shared queries', () => { expect(isSharedDashboardQuery('-- Dashboard --')).toBe(true); diff --git a/public/app/plugins/datasource/dashboard/runSharedRequest.ts b/public/app/plugins/datasource/dashboard/runSharedRequest.ts index 6ec3412ac17..4b7ee2782b1 100644 --- a/public/app/plugins/datasource/dashboard/runSharedRequest.ts +++ b/public/app/plugins/datasource/dashboard/runSharedRequest.ts @@ -1,7 +1,5 @@ import { Observable } from 'rxjs'; -import { QueryRunnerOptions } from 'app/features/query/state/PanelQueryRunner'; -import { DashboardQuery, SHARED_DASHBOARD_QUERY } from './types'; -import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; + import { DataQuery, DataQueryRequest, @@ -11,6 +9,10 @@ import { LoadingState, PanelData, } from '@grafana/data'; +import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { QueryRunnerOptions } from 'app/features/query/state/PanelQueryRunner'; + +import { DashboardQuery, SHARED_DASHBOARD_QUERY } from './types'; export function isSharedDashboardQuery(datasource: string | DataSourceRef | DataSourceApi | null) { if (!datasource) { diff --git a/public/app/plugins/datasource/elasticsearch/components/AddRemove.test.tsx b/public/app/plugins/datasource/elasticsearch/components/AddRemove.test.tsx index 7b7dd7dfec9..8c2feee10ab 100644 --- a/public/app/plugins/datasource/elasticsearch/components/AddRemove.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/AddRemove.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { AddRemove } from './AddRemove'; const noop = () => {}; diff --git a/public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx b/public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx index 4e210830ea0..935167f9ae0 100644 --- a/public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/AddRemove.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/css'; import React from 'react'; + import { IconButton } from './IconButton'; interface Props { diff --git a/public/app/plugins/datasource/elasticsearch/components/IconButton.tsx b/public/app/plugins/datasource/elasticsearch/components/IconButton.tsx index 51fb6b179ac..8758f405cd1 100644 --- a/public/app/plugins/datasource/elasticsearch/components/IconButton.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/IconButton.tsx @@ -1,7 +1,8 @@ -import { Icon } from '@grafana/ui'; import { cx, css } from '@emotion/css'; import React, { ComponentProps, ButtonHTMLAttributes } from 'react'; +import { Icon } from '@grafana/ui'; + const SROnly = css` clip: rect(0 0 0 0); clip-path: inset(50%); diff --git a/public/app/plugins/datasource/elasticsearch/components/MetricPicker.tsx b/public/app/plugins/datasource/elasticsearch/components/MetricPicker.tsx index 8a46e71b2f7..3c6c4e1824c 100644 --- a/public/app/plugins/datasource/elasticsearch/components/MetricPicker.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/MetricPicker.tsx @@ -1,8 +1,11 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { SelectableValue } from '@grafana/data'; import { Segment } from '@grafana/ui'; + import { describeMetric } from '../utils'; + import { MetricAggregation } from './QueryEditor/MetricAggregationsEditor/aggregations'; const noWrap = css` diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/BucketAggregationEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/BucketAggregationEditor.tsx index b1f069f5730..16a29633d50 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/BucketAggregationEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/BucketAggregationEditor.tsx @@ -1,11 +1,14 @@ +import React from 'react'; + import { SelectableValue } from '@grafana/data'; import { InlineSegmentGroup, Segment, SegmentAsync } from '@grafana/ui'; -import React from 'react'; + import { useFields } from '../../../hooks/useFields'; import { useDispatch } from '../../../hooks/useStatelessReducer'; import { segmentStyles } from '../styles'; -import { BucketAggregation, BucketAggregationType, isBucketAggregationWithField } from './aggregations'; + import { SettingsEditor } from './SettingsEditor'; +import { BucketAggregation, BucketAggregationType, isBucketAggregationWithField } from './aggregations'; import { changeBucketAggregationField, changeBucketAggregationType } from './state/actions'; import { bucketAggregationConfig } from './utils'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx index bae48b2e382..5a459dbe752 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx @@ -1,14 +1,17 @@ +import { uniqueId } from 'lodash'; import React, { useRef } from 'react'; import { GroupBase, OptionsOrGroups } from 'react-select'; -import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui'; -import { DateHistogram } from '../aggregations'; -import { bucketAggregationConfig } from '../utils'; -import { useDispatch } from '../../../../hooks/useStatelessReducer'; + import { InternalTimeZones, SelectableValue } from '@grafana/data'; -import { changeBucketAggregationSetting } from '../state/actions'; -import { inlineFieldProps } from '.'; -import { uniqueId } from 'lodash'; +import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui'; + +import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { useCreatableSelectPersistedBehaviour } from '../../../hooks/useCreatableSelectPersistedBehaviour'; +import { DateHistogram } from '../aggregations'; +import { changeBucketAggregationSetting } from '../state/actions'; +import { bucketAggregationConfig } from '../utils'; + +import { inlineFieldProps } from '.'; const defaultIntervalOptions: Array> = [ { label: 'auto', value: 'auto' }, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx index 883251e803e..2e26f480e9f 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/index.tsx @@ -1,13 +1,16 @@ -import { InlineField, Input, QueryField } from '@grafana/ui'; import { css } from '@emotion/css'; +import { uniqueId } from 'lodash'; import React, { useEffect, useRef } from 'react'; -import { AddRemove } from '../../../../AddRemove'; + +import { InlineField, Input, QueryField } from '@grafana/ui'; + import { useDispatch, useStatelessReducer } from '../../../../../hooks/useStatelessReducer'; +import { AddRemove } from '../../../../AddRemove'; import { Filters } from '../../aggregations'; import { changeBucketAggregationSetting } from '../../state/actions'; + import { addFilter, changeFilter, removeFilter } from './state/actions'; import { reducer as filtersReducer } from './state/reducer'; -import { uniqueId } from 'lodash'; interface Props { bucketAgg: Filters; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/actions.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/actions.ts index 90f823e9149..5c24b3dd003 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/actions.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/actions.ts @@ -1,4 +1,5 @@ import { createAction } from '@reduxjs/toolkit'; + import { Filter } from '../../../aggregations'; export const addFilter = createAction('@bucketAggregations/filter/add'); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.test.ts index ee947026028..aa532275d5e 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.test.ts @@ -1,5 +1,7 @@ import { reducerTester } from 'test/core/redux/reducerTester'; + import { Filter } from '../../../aggregations'; + import { addFilter, changeFilter, removeFilter } from './actions'; import { reducer } from './reducer'; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.ts index dfe5c518c21..05d89aaa6ef 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/FiltersSettingsEditor/state/reducer.ts @@ -1,6 +1,8 @@ import { Action } from 'redux'; + import { Filter } from '../../../aggregations'; import { defaultFilter } from '../utils'; + import { addFilter, changeFilter, removeFilter } from './actions'; export const reducer = (state: Filter[] = [], action: Action) => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx index 3243378b00a..7760ff681fa 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.test.tsx @@ -1,12 +1,15 @@ import { screen } from '@testing-library/react'; -import { ElasticsearchQuery } from '../../../../types'; import React from 'react'; +import selectEvent from 'react-select-event'; + +import { describeMetric } from 'app/plugins/datasource/elasticsearch/utils'; + import { renderWithESProvider } from '../../../../test-helpers/render'; +import { ElasticsearchQuery } from '../../../../types'; import { Average, Derivative, TopMetrics } from '../../MetricAggregationsEditor/aggregations'; import { Terms } from '../aggregations'; + import { TermsSettingsEditor } from './TermsSettingsEditor'; -import selectEvent from 'react-select-event'; -import { describeMetric } from 'app/plugins/datasource/elasticsearch/utils'; describe('Terms Settings Editor', () => { it('Pipeline aggregations should not be in "order by" options', () => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx index fba98d56409..1a7fc93eebb 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx @@ -1,14 +1,13 @@ +import { uniqueId } from 'lodash'; import React, { useRef } from 'react'; -import { InlineField, Select, Input } from '@grafana/ui'; -import { Terms } from '../aggregations'; -import { useDispatch } from '../../../../hooks/useStatelessReducer'; -import { inlineFieldProps } from '.'; -import { bucketAggregationConfig, orderByOptions, orderOptions, sizeOptions } from '../utils'; -import { useCreatableSelectPersistedBehaviour } from '../../../hooks/useCreatableSelectPersistedBehaviour'; -import { changeBucketAggregationSetting } from '../state/actions'; -import { useQuery } from '../../ElasticsearchQueryContext'; + import { SelectableValue } from '@grafana/data'; +import { InlineField, Select, Input } from '@grafana/ui'; + +import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { describeMetric } from '../../../../utils'; +import { useCreatableSelectPersistedBehaviour } from '../../../hooks/useCreatableSelectPersistedBehaviour'; +import { useQuery } from '../../ElasticsearchQueryContext'; import { ExtendedStatMetaType, ExtendedStats, @@ -16,7 +15,11 @@ import { MetricAggregation, Percentiles, } from '../../MetricAggregationsEditor/aggregations'; -import { uniqueId } from 'lodash'; +import { Terms } from '../aggregations'; +import { changeBucketAggregationSetting } from '../state/actions'; +import { bucketAggregationConfig, orderByOptions, orderOptions, sizeOptions } from '../utils'; + +import { inlineFieldProps } from '.'; interface Props { bucketAgg: Terms; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/index.tsx index a4b8051f93b..079a744e52e 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/index.tsx @@ -1,15 +1,18 @@ -import { InlineField, Input } from '@grafana/ui'; +import { uniqueId } from 'lodash'; import React, { ComponentProps, useRef } from 'react'; + +import { InlineField, Input } from '@grafana/ui'; + import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { SettingsEditorContainer } from '../../SettingsEditorContainer'; -import { changeBucketAggregationSetting } from '../state/actions'; import { BucketAggregation } from '../aggregations'; +import { changeBucketAggregationSetting } from '../state/actions'; import { bucketAggregationConfig } from '../utils'; -import { FiltersSettingsEditor } from './FiltersSettingsEditor'; -import { useDescription } from './useDescription'; + import { DateHistogramSettingsEditor } from './DateHistogramSettingsEditor'; +import { FiltersSettingsEditor } from './FiltersSettingsEditor'; import { TermsSettingsEditor } from './TermsSettingsEditor'; -import { uniqueId } from 'lodash'; +import { useDescription } from './useDescription'; export const inlineFieldProps: Partial> = { labelWidth: 16, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/index.tsx index 428015f7071..969c90f9b08 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/index.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import { BucketAggregationEditor } from './BucketAggregationEditor'; + import { useDispatch } from '../../../hooks/useStatelessReducer'; -import { addBucketAggregation, removeBucketAggregation } from './state/actions'; -import { BucketAggregation } from './aggregations'; +import { IconButton } from '../../IconButton'; import { useQuery } from '../ElasticsearchQueryContext'; import { QueryEditorRow } from '../QueryEditorRow'; -import { IconButton } from '../../IconButton'; + +import { BucketAggregationEditor } from './BucketAggregationEditor'; +import { BucketAggregation } from './aggregations'; +import { addBucketAggregation, removeBucketAggregation } from './state/actions'; interface Props { nextId: BucketAggregation['id']; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/actions.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/actions.ts index 124577ba350..44d1efa68d5 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/actions.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/actions.ts @@ -1,4 +1,5 @@ import { createAction } from '@reduxjs/toolkit'; + import { BucketAggregation, BucketAggregationType, BucketAggregationWithField } from '../aggregations'; export const addBucketAggregation = createAction('@bucketAggs/add'); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts index b97ef8d913d..e85a55b7f0a 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.test.ts @@ -1,8 +1,13 @@ -import { defaultBucketAgg } from 'app/plugins/datasource/elasticsearch/query_def'; import { reducerTester } from 'test/core/redux/reducerTester'; + +import { defaultBucketAgg } from 'app/plugins/datasource/elasticsearch/query_def'; +import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types'; + import { changeMetricType } from '../../MetricAggregationsEditor/state/actions'; +import { initQuery } from '../../state'; import { BucketAggregation, DateHistogram } from '../aggregations'; import { bucketAggregationConfig } from '../utils'; + import { addBucketAggregation, changeBucketAggregationField, @@ -11,8 +16,6 @@ import { removeBucketAggregation, } from './actions'; import { createReducer } from './reducer'; -import { initQuery } from '../../state'; -import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types'; describe('Bucket Aggregations Reducer', () => { it('Should correctly add new aggregations', () => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts index 5456e937198..cc62fee5d54 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts @@ -1,11 +1,14 @@ +import { Action } from '@reduxjs/toolkit'; + import { defaultBucketAgg } from '../../../../query_def'; import { ElasticsearchQuery } from '../../../../types'; -import { metricAggregationConfig } from '../../MetricAggregationsEditor/utils'; -import { BucketAggregation, Terms } from '../aggregations'; -import { initQuery } from '../../state'; -import { bucketAggregationConfig } from '../utils'; import { removeEmpty } from '../../../../utils'; -import { Action } from '@reduxjs/toolkit'; +import { changeMetricType } from '../../MetricAggregationsEditor/state/actions'; +import { metricAggregationConfig } from '../../MetricAggregationsEditor/utils'; +import { initQuery } from '../../state'; +import { BucketAggregation, Terms } from '../aggregations'; +import { bucketAggregationConfig } from '../utils'; + import { addBucketAggregation, changeBucketAggregationField, @@ -13,7 +16,6 @@ import { changeBucketAggregationType, removeBucketAggregation, } from './actions'; -import { changeMetricType } from '../../MetricAggregationsEditor/state/actions'; export const createReducer = (defaultTimeField: string) => diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/utils.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/utils.ts index 56673b91236..d37876d090e 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/utils.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/utils.ts @@ -1,7 +1,9 @@ -import { BucketsConfiguration } from '../../../types'; -import { defaultFilter } from './SettingsEditor/FiltersSettingsEditor/utils'; import { InternalTimeZones, SelectableValue } from '@grafana/data'; +import { BucketsConfiguration } from '../../../types'; + +import { defaultFilter } from './SettingsEditor/FiltersSettingsEditor/utils'; + export const bucketAggregationConfig: BucketsConfiguration = { terms: { label: 'Terms', diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.test.tsx index d95eb6f4678..1ba546ccac2 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.test.tsx @@ -1,11 +1,14 @@ -import React, { PropsWithChildren } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; import { render } from '@testing-library/react'; -import { ElasticsearchProvider, useQuery } from './ElasticsearchQueryContext'; -import { ElasticsearchQuery } from '../../types'; -import { ElasticDatasource } from '../../datasource'; +import { renderHook } from '@testing-library/react-hooks'; +import React, { PropsWithChildren } from 'react'; + import { getDefaultTimeRange } from '@grafana/data'; +import { ElasticDatasource } from '../../datasource'; +import { ElasticsearchQuery } from '../../types'; + +import { ElasticsearchProvider, useQuery } from './ElasticsearchQueryContext'; + const query: ElasticsearchQuery = { refId: 'A', query: '', diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.tsx index 1f7be0820d6..77fbf6966c8 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/ElasticsearchQueryContext.tsx @@ -1,12 +1,14 @@ import React, { Context, createContext, PropsWithChildren, useCallback, useContext } from 'react'; + +import { TimeRange } from '@grafana/data'; + import { ElasticDatasource } from '../../datasource'; import { combineReducers, useStatelessReducer, DispatchContext } from '../../hooks/useStatelessReducer'; import { ElasticsearchQuery } from '../../types'; -import { reducer as metricsReducer } from './MetricAggregationsEditor/state/reducer'; import { createReducer as createBucketAggsReducer } from './BucketAggregationsEditor/state/reducer'; +import { reducer as metricsReducer } from './MetricAggregationsEditor/state/reducer'; import { aliasPatternReducer, queryReducer, initQuery } from './state'; -import { TimeRange } from '@grafana/data'; const DatasourceContext = createContext(undefined); const QueryContext = createContext(undefined); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx index 1e9ab8c9862..df7b812ed98 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.test.tsx @@ -1,14 +1,17 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; -import { ElasticsearchProvider } from '../ElasticsearchQueryContext'; -import { MetricEditor } from './MetricEditor'; import React, { ReactNode, PropsWithChildren } from 'react'; -import { ElasticDatasource } from '../../../datasource'; -import { getDefaultTimeRange } from '@grafana/data'; -import { ElasticsearchQuery } from '../../../types'; -import { Average, UniqueCount } from './aggregations'; -import { defaultBucketAgg, defaultMetricAgg } from '../../../query_def'; import { from } from 'rxjs'; +import { getDefaultTimeRange } from '@grafana/data'; + +import { ElasticDatasource } from '../../../datasource'; +import { defaultBucketAgg, defaultMetricAgg } from '../../../query_def'; +import { ElasticsearchQuery } from '../../../types'; +import { ElasticsearchProvider } from '../ElasticsearchQueryContext'; + +import { MetricEditor } from './MetricEditor'; +import { Average, UniqueCount } from './aggregations'; + describe('Metric Editor', () => { it('Should display a "None" option for "field" if the metric supports inline script', async () => { const avg: Average = { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx index 2c4d5619b68..b8b57fb7342 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx @@ -1,15 +1,17 @@ -import { SelectableValue } from '@grafana/data'; -import { InlineSegmentGroup, Segment, SegmentAsync, useTheme2 } from '@grafana/ui'; import { cx } from '@emotion/css'; import React, { useCallback } from 'react'; -import { useDatasource, useQuery } from '../ElasticsearchQueryContext'; +import { satisfies } from 'semver'; + +import { SelectableValue } from '@grafana/data'; +import { InlineSegmentGroup, Segment, SegmentAsync, useTheme2 } from '@grafana/ui'; + +import { useFields } from '../../../hooks/useFields'; import { useDispatch } from '../../../hooks/useStatelessReducer'; -import { getStyles } from './styles'; -import { SettingsEditor } from './SettingsEditor'; -import { metricAggregationConfig } from './utils'; -import { changeMetricField, changeMetricType } from './state/actions'; import { MetricPicker } from '../../MetricPicker'; +import { useDatasource, useQuery } from '../ElasticsearchQueryContext'; import { segmentStyles } from '../styles'; + +import { SettingsEditor } from './SettingsEditor'; import { isMetricAggregationWithField, isMetricAggregationWithInlineScript, @@ -19,8 +21,9 @@ import { MetricAggregation, MetricAggregationType, } from './aggregations'; -import { useFields } from '../../../hooks/useFields'; -import { satisfies } from 'semver'; +import { changeMetricField, changeMetricType } from './state/actions'; +import { getStyles } from './styles'; +import { metricAggregationConfig } from './utils'; const toOption = (metric: MetricAggregation) => ({ label: metricAggregationConfig[metric.type].label, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx index a0780c6b15f..23b1a763ebb 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/index.tsx @@ -1,20 +1,23 @@ -import React, { Fragment, useEffect } from 'react'; -import { Input, InlineLabel } from '@grafana/ui'; -import { changeMetricAttribute } from '../../state/actions'; import { css } from '@emotion/css'; -import { AddRemove } from '../../../../AddRemove'; +import { uniqueId } from 'lodash'; +import React, { Fragment, useEffect } from 'react'; + +import { Input, InlineLabel } from '@grafana/ui'; + import { useStatelessReducer, useDispatch } from '../../../../../hooks/useStatelessReducer'; +import { AddRemove } from '../../../../AddRemove'; import { MetricPicker } from '../../../../MetricPicker'; -import { reducer } from './state/reducer'; +import { BucketScript, MetricAggregation } from '../../aggregations'; +import { changeMetricAttribute } from '../../state/actions'; +import { SettingField } from '../SettingField'; + import { addPipelineVariable, removePipelineVariable, renamePipelineVariable, changePipelineVariableMetric, } from './state/actions'; -import { SettingField } from '../SettingField'; -import { BucketScript, MetricAggregation } from '../../aggregations'; -import { uniqueId } from 'lodash'; +import { reducer } from './state/reducer'; interface Props { value: BucketScript; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts index df26707ce18..ad8e45702e2 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.test.ts @@ -1,5 +1,7 @@ import { reducerTester } from 'test/core/redux/reducerTester'; + import { PipelineVariable } from '../../../aggregations'; + import { addPipelineVariable, changePipelineVariableMetric, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.ts index 563189fe426..3312758021a 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/BucketScriptSettingsEditor/state/reducer.ts @@ -1,6 +1,8 @@ import { Action } from '@reduxjs/toolkit'; + import { PipelineVariable } from '../../../aggregations'; import { defaultPipelineVariable, generatePipelineVariableName } from '../utils'; + import { addPipelineVariable, changePipelineVariableMetric, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/MovingAverageSettingsEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/MovingAverageSettingsEditor.tsx index 98e208042a1..4d0793888e5 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/MovingAverageSettingsEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/MovingAverageSettingsEditor.tsx @@ -1,10 +1,13 @@ -import { Input, InlineField, Select, InlineSwitch } from '@grafana/ui'; import { uniqueId } from 'lodash'; import React, { useRef } from 'react'; + +import { Input, InlineField, Select, InlineSwitch } from '@grafana/ui'; + import { useDispatch } from '../../../../hooks/useStatelessReducer'; import { movingAvgModelOptions } from '../../../../query_def'; import { isEWMAMovingAverage, isHoltMovingAverage, isHoltWintersMovingAverage, MovingAverage } from '../aggregations'; import { changeMetricSetting } from '../state/actions'; + import { SettingField } from './SettingField'; interface Props { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx index e25ff580464..2c44c6d80cb 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx @@ -1,11 +1,13 @@ +import { uniqueId } from 'lodash'; import React, { ComponentProps, useState } from 'react'; + import { InlineField, Input } from '@grafana/ui'; +import { getScriptValue } from 'app/plugins/datasource/elasticsearch/utils'; + import { useDispatch } from '../../../../hooks/useStatelessReducer'; -import { changeMetricSetting } from '../state/actions'; import { SettingKeyOf } from '../../../types'; import { MetricAggregationWithInlineScript, MetricAggregationWithSettings } from '../aggregations'; -import { uniqueId } from 'lodash'; -import { getScriptValue } from 'app/plugins/datasource/elasticsearch/utils'; +import { changeMetricSetting } from '../state/actions'; interface Props> { label: string; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/TopMetricsSettingsEditor.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/TopMetricsSettingsEditor.tsx index f8f5c5ba63d..2b3a5d88255 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/TopMetricsSettingsEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/TopMetricsSettingsEditor.tsx @@ -1,12 +1,14 @@ -import { AsyncMultiSelect, InlineField, SegmentAsync, Select } from '@grafana/ui'; +import { css } from '@emotion/css'; import React, { FunctionComponent } from 'react'; -import { useDispatch } from '../../../../hooks/useStatelessReducer'; + +import { SelectableValue } from '@grafana/data'; +import { AsyncMultiSelect, InlineField, SegmentAsync, Select } from '@grafana/ui'; + import { useFields } from '../../../../hooks/useFields'; +import { useDispatch } from '../../../../hooks/useStatelessReducer'; +import { orderOptions } from '../../BucketAggregationsEditor/utils'; import { TopMetrics } from '../aggregations'; import { changeMetricSetting } from '../state/actions'; -import { orderOptions } from '../../BucketAggregationsEditor/utils'; -import { css } from '@emotion/css'; -import { SelectableValue } from '@grafana/data'; interface Props { metric: TopMetrics; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.test.tsx index 88c8a3aa23b..f3753a70540 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { SettingsEditor } from '.'; -import { ElasticsearchProvider } from '../../ElasticsearchQueryContext'; +import React from 'react'; + +import { getDefaultTimeRange } from '@grafana/data'; + import { ElasticDatasource } from '../../../../datasource'; import { ElasticsearchQuery } from '../../../../types'; -import { getDefaultTimeRange } from '@grafana/data'; +import { ElasticsearchProvider } from '../../ElasticsearchQueryContext'; + +import { SettingsEditor } from '.'; describe('Settings Editor', () => { describe('Raw Data', () => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.tsx index af31e49b830..50f7f82394a 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/index.tsx @@ -1,23 +1,26 @@ -import { InlineField, Input, InlineSwitch, Select } from '@grafana/ui'; +import { uniqueId } from 'lodash'; import React, { ComponentProps, useRef, useState } from 'react'; -import { extendedStats } from '../../../../query_def'; + +import { InlineField, Input, InlineSwitch, Select } from '@grafana/ui'; + import { useDispatch } from '../../../../hooks/useStatelessReducer'; -import { changeMetricMeta, changeMetricSetting } from '../state/actions'; +import { extendedStats } from '../../../../query_def'; +import { useQuery } from '../../ElasticsearchQueryContext'; +import { SettingsEditorContainer } from '../../SettingsEditorContainer'; import { MetricAggregation, isMetricAggregationWithInlineScript, isMetricAggregationWithMissingSupport, ExtendedStat, } from '../aggregations'; -import { BucketScriptSettingsEditor } from './BucketScriptSettingsEditor'; -import { SettingField } from './SettingField'; -import { SettingsEditorContainer } from '../../SettingsEditorContainer'; -import { useDescription } from './useDescription'; -import { MovingAverageSettingsEditor } from './MovingAverageSettingsEditor'; -import { TopMetricsSettingsEditor } from './TopMetricsSettingsEditor'; -import { uniqueId } from 'lodash'; +import { changeMetricMeta, changeMetricSetting } from '../state/actions'; import { metricAggregationConfig } from '../utils'; -import { useQuery } from '../../ElasticsearchQueryContext'; + +import { BucketScriptSettingsEditor } from './BucketScriptSettingsEditor'; +import { MovingAverageSettingsEditor } from './MovingAverageSettingsEditor'; +import { SettingField } from './SettingField'; +import { TopMetricsSettingsEditor } from './TopMetricsSettingsEditor'; +import { useDescription } from './useDescription'; // TODO: Move this somewhere and share it with BucketsAggregation Editor const inlineFieldProps: Partial> = { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx index f00699af7f5..bd607a814dd 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/index.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import { MetricEditor } from './MetricEditor'; + import { useDispatch } from '../../../hooks/useStatelessReducer'; -import { metricAggregationConfig } from './utils'; -import { addMetric, removeMetric, toggleMetricVisibility } from './state/actions'; -import { MetricAggregation } from './aggregations'; +import { IconButton } from '../../IconButton'; import { useQuery } from '../ElasticsearchQueryContext'; import { QueryEditorRow } from '../QueryEditorRow'; -import { IconButton } from '../../IconButton'; + +import { MetricEditor } from './MetricEditor'; +import { MetricAggregation } from './aggregations'; +import { addMetric, removeMetric, toggleMetricVisibility } from './state/actions'; +import { metricAggregationConfig } from './utils'; interface Props { nextId: MetricAggregation['id']; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/actions.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/actions.ts index 8b52fa01df3..024ae207a71 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/actions.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/actions.ts @@ -1,4 +1,5 @@ import { createAction } from '@reduxjs/toolkit'; + import { MetricAggregation, MetricAggregationWithMeta, MetricAggregationWithSettings } from '../aggregations'; export const addMetric = createAction('@metrics/add'); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts index e9ee62bfec2..12ccc4c167b 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.test.ts @@ -1,5 +1,12 @@ import { reducerTester } from 'test/core/redux/reducerTester'; -import { reducer } from './reducer'; + +import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types'; + +import { defaultMetricAgg } from '../../../../query_def'; +import { initQuery } from '../../state'; +import { Derivative, ExtendedStats, MetricAggregation } from '../aggregations'; +import { metricAggregationConfig } from '../utils'; + import { addMetric, changeMetricAttribute, @@ -10,11 +17,7 @@ import { removeMetric, toggleMetricVisibility, } from './actions'; -import { Derivative, ExtendedStats, MetricAggregation } from '../aggregations'; -import { defaultMetricAgg } from '../../../../query_def'; -import { metricAggregationConfig } from '../utils'; -import { initQuery } from '../../state'; -import { ElasticsearchQuery } from 'app/plugins/datasource/elasticsearch/types'; +import { reducer } from './reducer'; describe('Metric Aggregations Reducer', () => { it('should correctly add new aggregations', () => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts index c1919640f9a..9f58c7a40bb 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts @@ -1,4 +1,5 @@ import { Action } from '@reduxjs/toolkit'; + import { defaultMetricAgg } from '../../../../query_def'; import { ElasticsearchQuery } from '../../../../types'; import { removeEmpty } from '../../../../utils'; @@ -10,6 +11,7 @@ import { MetricAggregation, } from '../aggregations'; import { getChildren, metricAggregationConfig } from '../utils'; + import { addMetric, changeMetricAttribute, diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/styles.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/styles.ts index 603997462c5..7497dec0d7b 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/styles.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/styles.ts @@ -1,6 +1,7 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; + export const getStyles = (theme: GrafanaTheme2, hidden: boolean) => { return { color: diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts index 2828ee85c34..ae5b8cee37f 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/utils.ts @@ -1,14 +1,15 @@ import { MetricsConfiguration } from '../../../types'; + +import { + defaultPipelineVariable, + generatePipelineVariableName, +} from './SettingsEditor/BucketScriptSettingsEditor/utils'; import { isMetricAggregationWithField, isPipelineAggregationWithMultipleBucketPaths, MetricAggregation, PipelineMetricAggregationType, } from './aggregations'; -import { - defaultPipelineVariable, - generatePipelineVariableName, -} from './SettingsEditor/BucketScriptSettingsEditor/utils'; export const metricAggregationConfig: MetricsConfiguration = { count: { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorRow.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorRow.tsx index 9b9af1a5667..155ed2942bc 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorRow.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/QueryEditorRow.tsx @@ -1,9 +1,10 @@ -import { GrafanaTheme2 } from '@grafana/data'; -import { IconButton, InlineFieldRow, InlineLabel, InlineSegmentGroup, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; import { noop } from 'lodash'; import React, { PropsWithChildren } from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; +import { IconButton, InlineFieldRow, InlineLabel, InlineSegmentGroup, useStyles2 } from '@grafana/ui'; + interface Props { label: string; onRemoveClick?: false | (() => void); diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx index 6dbbcc0a239..a7d64e95f93 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/SettingsEditorContainer.tsx @@ -1,7 +1,9 @@ -import { GrafanaTheme2 } from '@grafana/data'; -import { Icon, InlineSegmentGroup, useTheme2 } from '@grafana/ui'; import { css, cx } from '@emotion/css'; import React, { PropsWithChildren, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, InlineSegmentGroup, useTheme2 } from '@grafana/ui'; + import { segmentStyles } from './styles'; const getStyles = (theme: GrafanaTheme2, hidden: boolean) => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx index 73e002b87c8..24775ff5691 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.test.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { QueryEditor } from '.'; +import React from 'react'; + import { ElasticDatasource } from '../../datasource'; import { ElasticsearchQuery } from '../../types'; +import { QueryEditor } from '.'; + const noop = () => void 0; describe('QueryEditor', () => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx index 947f86cb7c3..1e7c9d32d56 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/index.tsx @@ -1,16 +1,19 @@ -import React from 'react'; -import { getDefaultTimeRange, GrafanaTheme2, QueryEditorProps } from '@grafana/data'; -import { ElasticDatasource } from '../../datasource'; -import { ElasticsearchOptions, ElasticsearchQuery } from '../../types'; -import { ElasticsearchProvider } from './ElasticsearchQueryContext'; -import { InlineField, InlineLabel, Input, QueryField, useStyles2 } from '@grafana/ui'; -import { changeAliasPattern, changeQuery } from './state'; -import { MetricAggregationsEditor } from './MetricAggregationsEditor'; -import { BucketAggregationsEditor } from './BucketAggregationsEditor'; -import { useDispatch } from '../../hooks/useStatelessReducer'; -import { useNextId } from '../../hooks/useNextId'; -import { metricAggregationConfig } from './MetricAggregationsEditor/utils'; import { css } from '@emotion/css'; +import React from 'react'; + +import { getDefaultTimeRange, GrafanaTheme2, QueryEditorProps } from '@grafana/data'; +import { InlineField, InlineLabel, Input, QueryField, useStyles2 } from '@grafana/ui'; + +import { ElasticDatasource } from '../../datasource'; +import { useNextId } from '../../hooks/useNextId'; +import { useDispatch } from '../../hooks/useStatelessReducer'; +import { ElasticsearchOptions, ElasticsearchQuery } from '../../types'; + +import { BucketAggregationsEditor } from './BucketAggregationsEditor'; +import { ElasticsearchProvider } from './ElasticsearchQueryContext'; +import { MetricAggregationsEditor } from './MetricAggregationsEditor'; +import { metricAggregationConfig } from './MetricAggregationsEditor/utils'; +import { changeAliasPattern, changeQuery } from './state'; export type ElasticQueryEditorProps = QueryEditorProps; diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts index 6cb0b041b4e..bb7ea5a161f 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.test.ts @@ -1,5 +1,7 @@ import { reducerTester } from 'test/core/redux/reducerTester'; + import { ElasticsearchQuery } from '../../types'; + import { aliasPatternReducer, changeAliasPattern, changeQuery, initQuery, queryReducer } from './state'; describe('Query Reducer', () => { diff --git a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.ts b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.ts index 8601a3b0180..98182f056d6 100644 --- a/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.ts +++ b/public/app/plugins/datasource/elasticsearch/components/QueryEditor/state.ts @@ -1,4 +1,5 @@ import { Action, createAction } from '@reduxjs/toolkit'; + import { ElasticsearchQuery } from '../../types'; /** diff --git a/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.test.tsx b/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.test.tsx index 8b5f9cb5ede..6196f538acd 100644 --- a/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { Select, InlineField } from '@grafana/ui'; -import { useCreatableSelectPersistedBehaviour } from './useCreatableSelectPersistedBehaviour'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { Select, InlineField } from '@grafana/ui'; + +import { useCreatableSelectPersistedBehaviour } from './useCreatableSelectPersistedBehaviour'; describe('useCreatableSelectPersistedBehaviour', () => { - it('Should make a Select accept custom values', () => { + it('Should make a Select accept custom values', async () => { const MyComp = (_: { force?: boolean }) => ( { expect(input).toBeInTheDocument(); // we open the menu - userEvent.click(input); + await userEvent.click(input); // we expect 2 elemnts having "Option 2": the input itself and the option. expect(screen.getAllByText('Option 2')).toHaveLength(2); diff --git a/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.ts b/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.ts index fd94ac0ce21..6cebb31516d 100644 --- a/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.ts +++ b/public/app/plugins/datasource/elasticsearch/components/hooks/useCreatableSelectPersistedBehaviour.ts @@ -1,6 +1,7 @@ -import { SelectableValue } from '@grafana/data'; import { useState } from 'react'; +import { SelectableValue } from '@grafana/data'; + const hasValue = (searchValue: T['value']) => ({ value }: T) => diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx index ecf6cc97ea3..ce6f7a4c2b5 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; import { mount, shallow } from 'enzyme'; -import { ConfigEditor } from './ConfigEditor'; +import React from 'react'; + import { DataSourceHttpSettings } from '@grafana/ui'; + +import { ConfigEditor } from './ConfigEditor'; import { ElasticDetails } from './ElasticDetails'; import { LogsConfig } from './LogsConfig'; import { createDefaultConfigOptions } from './mocks'; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx index 003f19efed3..b1c7b9e6236 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx @@ -1,11 +1,14 @@ import React, { useEffect } from 'react'; -import { Alert, DataSourceHttpSettings } from '@grafana/ui'; + import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import { Alert, DataSourceHttpSettings } from '@grafana/ui'; +import { config } from 'app/core/config'; + import { ElasticsearchOptions } from '../types'; + +import { DataLinks } from './DataLinks'; import { ElasticDetails } from './ElasticDetails'; import { LogsConfig } from './LogsConfig'; -import { DataLinks } from './DataLinks'; -import { config } from 'app/core/config'; import { coerceOptions, isValidOptions } from './utils'; export type Props = DataSourcePluginOptionsEditorProps; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx b/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx index cbd29a74d3b..69cd5fb740a 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx @@ -1,11 +1,14 @@ -import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { css } from '@emotion/css'; +import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { usePrevious } from 'react-use'; + import { VariableSuggestion } from '@grafana/data'; import { DataSourcePicker } from '@grafana/runtime'; import { Button, LegacyForms, DataLinkInput, stylesFactory } from '@grafana/ui'; -const { FormField, Switch } = LegacyForms; + import { DataLinkConfig } from '../types'; -import { usePrevious } from 'react-use'; + +const { FormField, Switch } = LegacyForms; const getStyles = stylesFactory(() => ({ firstRow: css` diff --git a/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx index d6665b89b94..7e33ac2e2af 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { DataLinks } from './DataLinks'; -import { Button } from '@grafana/ui'; -import { DataLink } from './DataLink'; +import React from 'react'; import { act } from 'react-dom/test-utils'; +import { Button } from '@grafana/ui'; + +import { DataLink } from './DataLink'; +import { DataLinks } from './DataLinks'; + describe('DataLinks', () => { let originalGetSelection: typeof window.getSelection; beforeAll(() => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx b/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx index cb92fcf5475..910cb5cb8e2 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx @@ -1,8 +1,11 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { Button, useStyles2 } from '@grafana/ui'; +import React from 'react'; + import { GrafanaTheme2, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data'; +import { Button, useStyles2 } from '@grafana/ui'; + import { DataLinkConfig } from '../types'; + import { DataLink } from './DataLink'; const getStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx index 95996d3b892..444c41707e9 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx @@ -1,8 +1,9 @@ +import { render, screen } from '@testing-library/react'; import React from 'react'; +import selectEvent from 'react-select-event'; + import { ElasticDetails } from './ElasticDetails'; import { createDefaultConfigOptions } from './mocks'; -import { render, screen } from '@testing-library/react'; -import selectEvent from 'react-select-event'; describe('ElasticDetails', () => { describe('Max concurrent Shard Requests', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx index 9fa9ef356d3..117e97b3e18 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import { FieldSet, InlineField, Input, Select, InlineSwitch } from '@grafana/ui'; -import { ElasticsearchOptions, Interval } from '../types'; -import { DataSourceSettings, SelectableValue } from '@grafana/data'; import { gte, lt, valid } from 'semver'; + +import { DataSourceSettings, SelectableValue } from '@grafana/data'; +import { FieldSet, InlineField, Input, Select, InlineSwitch } from '@grafana/ui'; + +import { ElasticsearchOptions, Interval } from '../types'; + import { isTruthy } from './utils'; const indexPatternTypes: Array> = [ diff --git a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx index 4e967640d1d..861ed7cdfc1 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; +import React from 'react'; + import { LogsConfig } from './LogsConfig'; import { createDefaultConfigOptions } from './mocks'; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.tsx b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.tsx index 9694cc3759d..3f5d64038bf 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { Input, InlineField, FieldSet } from '@grafana/ui'; + import { ElasticsearchOptions } from '../types'; type Props = { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts b/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts index f80d702d3e3..1d63b322bbe 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts +++ b/public/app/plugins/datasource/elasticsearch/configuration/mocks.ts @@ -1,6 +1,7 @@ import { DataSourceSettings } from '@grafana/data'; -import { ElasticsearchOptions } from '../types'; + import { createDatasourceSettings } from '../../../../features/datasources/mocks'; +import { ElasticsearchOptions } from '../types'; export function createDefaultConfigOptions( options?: Partial diff --git a/public/app/plugins/datasource/elasticsearch/configuration/utils.ts b/public/app/plugins/datasource/elasticsearch/configuration/utils.ts index e88d768e8f5..d476a0801a0 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/utils.ts +++ b/public/app/plugins/datasource/elasticsearch/configuration/utils.ts @@ -1,7 +1,10 @@ -import { DataSourceSettings } from '@grafana/data'; import { valid } from 'semver'; + +import { DataSourceSettings } from '@grafana/data'; + import { ElasticsearchOptions } from '../types'; import { coerceESVersion } from '../utils'; + import { defaultMaxConcurrentShardRequests } from './ElasticDetails'; export const coerceOptions = ( diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index 4881f057dc3..dca0bbfe1be 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -1,5 +1,6 @@ import { map } from 'lodash'; import { Observable, of, throwError } from 'rxjs'; + import { ArrayVector, CoreApp, @@ -16,13 +17,14 @@ import { toUtc, } from '@grafana/data'; import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; - -import { ElasticDatasource, enhanceDataFrame } from './datasource'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ -import { ElasticsearchOptions, ElasticsearchQuery } from './types'; -import { Filters } from './components/QueryEditor/BucketAggregationsEditor/aggregations'; + import { createFetchResponse } from '../../../../test/helpers/createFetchResponse'; +import { Filters } from './components/QueryEditor/BucketAggregationsEditor/aggregations'; +import { ElasticDatasource, enhanceDataFrame } from './datasource'; +import { ElasticsearchOptions, ElasticsearchQuery } from './types'; + const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index d9837dcf364..98d5f91399b 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -2,7 +2,7 @@ import { cloneDeep, find, first as _first, isNumber, isObject, isString, map as import { generate, lastValueFrom, Observable, of, throwError } from 'rxjs'; import { catchError, first, map, mergeMap, skipWhile, throwIfEmpty } from 'rxjs/operators'; import { gte, lt, satisfies } from 'semver'; -import { BackendSrvRequest, getBackendSrv, getDataSourceSrv } from '@grafana/runtime'; + import { DataFrame, DataLink, @@ -26,27 +26,29 @@ import { TimeRange, toUtc, } from '@grafana/data'; -import LanguageProvider from './language_provider'; -import { ElasticResponse } from './elastic_response'; -import { IndexPattern } from './index_pattern'; -import { ElasticQueryBuilder } from './query_builder'; -import { defaultBucketAgg, hasMetricOfType } from './query_def'; -import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery, TermsQuery } from './types'; +import { BackendSrvRequest, getBackendSrv, getDataSourceSrv } from '@grafana/runtime'; import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; -import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; +import { queryLogsVolume } from 'app/core/logs_model'; +import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; + +import { + BucketAggregation, + isBucketAggregationWithField, +} from './components/QueryEditor/BucketAggregationsEditor/aggregations'; +import { bucketAggregationConfig } from './components/QueryEditor/BucketAggregationsEditor/utils'; import { isMetricAggregationWithField, isPipelineAggregationWithMultipleBucketPaths, Logs, } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; -import { bucketAggregationConfig } from './components/QueryEditor/BucketAggregationsEditor/utils'; -import { - BucketAggregation, - isBucketAggregationWithField, -} from './components/QueryEditor/BucketAggregationsEditor/aggregations'; +import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; +import { ElasticResponse } from './elastic_response'; +import { IndexPattern } from './index_pattern'; +import LanguageProvider from './language_provider'; +import { ElasticQueryBuilder } from './query_builder'; +import { defaultBucketAgg, hasMetricOfType } from './query_def'; +import { DataLinkConfig, ElasticsearchOptions, ElasticsearchQuery, TermsQuery } from './types'; import { coerceESVersion, getScriptValue } from './utils'; -import { queryLogsVolume } from 'app/core/logs_model'; // Those are metadata fields as defined in https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-fields.html#_identity_metadata_fields. // custom fields can start with underscores, therefore is not safe to exclude anything that starts with one. diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.ts index b52d36e85d6..6d2952e35e5 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.ts +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.ts @@ -1,7 +1,5 @@ import { clone, filter, find, identity, isArray, keys, map, uniq, values as _values } from 'lodash'; -import flatten from 'app/core/utils/flatten'; -import * as queryDef from './query_def'; -import TableModel from 'app/core/table_model'; + import { DataQueryResponse, DataFrame, @@ -10,14 +8,18 @@ import { MutableDataFrame, PreferredVisualisationType, } from '@grafana/data'; -import { ElasticsearchAggregation, ElasticsearchQuery } from './types'; +import TableModel from 'app/core/table_model'; +import flatten from 'app/core/utils/flatten'; + import { ExtendedStatMetaType, isMetricAggregationWithField, TopMetrics, } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; -import { describeMetric, getScriptValue } from './utils'; import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; +import * as queryDef from './query_def'; +import { ElasticsearchAggregation, ElasticsearchQuery } from './types'; +import { describeMetric, getScriptValue } from './utils'; const HIGHLIGHT_TAGS_EXP = `${queryDef.highlightTags.pre}([^@]+)${queryDef.highlightTags.post}`; type TopMetricMetric = Record; diff --git a/public/app/plugins/datasource/elasticsearch/hooks/useFields.test.tsx b/public/app/plugins/datasource/elasticsearch/hooks/useFields.test.tsx index 0af4be1bb55..33ee2cea2e4 100644 --- a/public/app/plugins/datasource/elasticsearch/hooks/useFields.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/hooks/useFields.test.tsx @@ -1,14 +1,17 @@ -import React, { PropsWithChildren } from 'react'; -import { ElasticDatasource } from '../datasource'; -import { from } from 'rxjs'; -import { ElasticsearchProvider } from '../components/QueryEditor/ElasticsearchQueryContext'; -import { getDefaultTimeRange } from '@grafana/data'; -import { ElasticsearchQuery } from '../types'; -import { defaultBucketAgg, defaultMetricAgg } from '../query_def'; import { renderHook } from '@testing-library/react-hooks'; -import { useFields } from './useFields'; -import { MetricAggregationType } from '../components/QueryEditor/MetricAggregationsEditor/aggregations'; +import React, { PropsWithChildren } from 'react'; +import { from } from 'rxjs'; + +import { getDefaultTimeRange } from '@grafana/data'; + import { BucketAggregationType } from '../components/QueryEditor/BucketAggregationsEditor/aggregations'; +import { ElasticsearchProvider } from '../components/QueryEditor/ElasticsearchQueryContext'; +import { MetricAggregationType } from '../components/QueryEditor/MetricAggregationsEditor/aggregations'; +import { ElasticDatasource } from '../datasource'; +import { defaultBucketAgg, defaultMetricAgg } from '../query_def'; +import { ElasticsearchQuery } from '../types'; + +import { useFields } from './useFields'; describe('useFields hook', () => { // TODO: If we move the field type to the configuration objects as described in the hook's source diff --git a/public/app/plugins/datasource/elasticsearch/hooks/useFields.ts b/public/app/plugins/datasource/elasticsearch/hooks/useFields.ts index 780b3c89fad..d4a75a1f3b9 100644 --- a/public/app/plugins/datasource/elasticsearch/hooks/useFields.ts +++ b/public/app/plugins/datasource/elasticsearch/hooks/useFields.ts @@ -1,5 +1,7 @@ import { lastValueFrom } from 'rxjs'; + import { MetricFindValue, SelectableValue } from '@grafana/data'; + import { BucketAggregationType, isBucketAggregationType, diff --git a/public/app/plugins/datasource/elasticsearch/hooks/useNextId.test.tsx b/public/app/plugins/datasource/elasticsearch/hooks/useNextId.test.tsx index 1da0067d016..fce53afa44a 100644 --- a/public/app/plugins/datasource/elasticsearch/hooks/useNextId.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/hooks/useNextId.test.tsx @@ -1,10 +1,13 @@ -import React, { PropsWithChildren } from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import { ElasticsearchProvider } from '../components/QueryEditor/ElasticsearchQueryContext'; -import { useNextId } from './useNextId'; -import { ElasticsearchQuery } from '../types'; +import React, { PropsWithChildren } from 'react'; + import { getDefaultTimeRange } from '@grafana/data'; +import { ElasticsearchProvider } from '../components/QueryEditor/ElasticsearchQueryContext'; +import { ElasticsearchQuery } from '../types'; + +import { useNextId } from './useNextId'; + describe('useNextId', () => { it('Should return the next available id', () => { const query: ElasticsearchQuery = { diff --git a/public/app/plugins/datasource/elasticsearch/hooks/useNextId.ts b/public/app/plugins/datasource/elasticsearch/hooks/useNextId.ts index 8d0d994e4c4..4a682c43b6a 100644 --- a/public/app/plugins/datasource/elasticsearch/hooks/useNextId.ts +++ b/public/app/plugins/datasource/elasticsearch/hooks/useNextId.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; -import { useQuery } from '../components/QueryEditor/ElasticsearchQueryContext'; + import { BucketAggregation } from '../components/QueryEditor/BucketAggregationsEditor/aggregations'; +import { useQuery } from '../components/QueryEditor/ElasticsearchQueryContext'; import { MetricAggregation } from '../components/QueryEditor/MetricAggregationsEditor/aggregations'; const toId = (e: T): T['id'] => e.id; diff --git a/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.test.tsx b/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.test.tsx index 2543888b23e..509f027d696 100644 --- a/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.test.tsx @@ -1,5 +1,6 @@ -import React, { PropsWithChildren } from 'react'; import { renderHook } from '@testing-library/react-hooks'; +import React, { PropsWithChildren } from 'react'; + import { useStatelessReducer, useDispatch, DispatchContext, combineReducers } from './useStatelessReducer'; describe('useStatelessReducer Hook', () => { diff --git a/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts b/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts index 4bd1f7c397b..1713f6d6cfb 100644 --- a/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts +++ b/public/app/plugins/datasource/elasticsearch/hooks/useStatelessReducer.ts @@ -1,5 +1,5 @@ -import { createContext, useCallback, useContext } from 'react'; import { Action } from '@reduxjs/toolkit'; +import { createContext, useCallback, useContext } from 'react'; export type Reducer = (state: S, action: A) => S; diff --git a/public/app/plugins/datasource/elasticsearch/index_pattern.ts b/public/app/plugins/datasource/elasticsearch/index_pattern.ts index 386ebd92396..bc95849a9fa 100644 --- a/public/app/plugins/datasource/elasticsearch/index_pattern.ts +++ b/public/app/plugins/datasource/elasticsearch/index_pattern.ts @@ -1,4 +1,5 @@ import { toUtc, dateTime, DateTime, DurationUnit } from '@grafana/data'; + import { Interval } from './types'; type IntervalMap = Record< diff --git a/public/app/plugins/datasource/elasticsearch/language_provider.test.ts b/public/app/plugins/datasource/elasticsearch/language_provider.test.ts index e5864cfd954..630b2fd700d 100644 --- a/public/app/plugins/datasource/elasticsearch/language_provider.test.ts +++ b/public/app/plugins/datasource/elasticsearch/language_provider.test.ts @@ -1,9 +1,11 @@ -import LanguageProvider from './language_provider'; -import { ElasticDatasource } from './datasource'; import { AbstractLabelOperator, AbstractQuery, DataSourceInstanceSettings } from '@grafana/data'; -import { ElasticsearchOptions, ElasticsearchQuery } from './types'; + import { TemplateSrv } from '../../../features/templating/template_srv'; +import { ElasticDatasource } from './datasource'; +import LanguageProvider from './language_provider'; +import { ElasticsearchOptions, ElasticsearchQuery } from './types'; + const templateSrvStub = { getAdhocFilters: jest.fn(() => [] as any[]), replace: jest.fn((a: string) => a), diff --git a/public/app/plugins/datasource/elasticsearch/module.ts b/public/app/plugins/datasource/elasticsearch/module.ts index 1ac22e392ab..055cc2c9d64 100644 --- a/public/app/plugins/datasource/elasticsearch/module.ts +++ b/public/app/plugins/datasource/elasticsearch/module.ts @@ -1,7 +1,8 @@ import { DataSourcePlugin } from '@grafana/data'; -import { ElasticDatasource } from './datasource'; -import { ConfigEditor } from './configuration/ConfigEditor'; + import { QueryEditor } from './components/QueryEditor'; +import { ConfigEditor } from './configuration/ConfigEditor'; +import { ElasticDatasource } from './datasource'; class ElasticAnnotationsQueryCtrl { static templateUrl = 'partials/annotations.editor.html'; diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.ts b/public/app/plugins/datasource/elasticsearch/query_builder.ts index 0f21c96b6f6..8727f3cb80d 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.ts +++ b/public/app/plugins/datasource/elasticsearch/query_builder.ts @@ -1,5 +1,7 @@ -import { InternalTimeZones } from '@grafana/data'; import { gte, lt } from 'semver'; + +import { InternalTimeZones } from '@grafana/data'; + import { Filters, Histogram, diff --git a/public/app/plugins/datasource/elasticsearch/specs/elastic_response.test.ts b/public/app/plugins/datasource/elasticsearch/specs/elastic_response.test.ts index d7377b8a805..6232fc526e9 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/elastic_response.test.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/elastic_response.test.ts @@ -1,8 +1,9 @@ import { DataFrameView, FieldCache, KeyValue, MutableDataFrame } from '@grafana/data'; -import { ElasticResponse } from '../elastic_response'; import flatten from 'app/core/utils/flatten'; -import { ElasticsearchQuery } from '../types'; + +import { ElasticResponse } from '../elastic_response'; import { highlightTags } from '../query_def'; +import { ElasticsearchQuery } from '../types'; describe('ElasticResponse', () => { let targets: ElasticsearchQuery[]; diff --git a/public/app/plugins/datasource/elasticsearch/specs/index_pattern.test.ts b/public/app/plugins/datasource/elasticsearch/specs/index_pattern.test.ts index 76a96794dd3..0fa98df9696 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/index_pattern.test.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/index_pattern.test.ts @@ -1,8 +1,9 @@ /// -import { IndexPattern } from '../index_pattern'; import { toUtc, getLocale, setLocale, dateTime } from '@grafana/data'; +import { IndexPattern } from '../index_pattern'; + describe('IndexPattern', () => { const originalLocale = getLocale(); afterEach(() => setLocale(originalLocale)); diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_builder.test.ts b/public/app/plugins/datasource/elasticsearch/specs/query_builder.test.ts index 1ed2b6d3540..f3c00db43cf 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/query_builder.test.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/query_builder.test.ts @@ -1,4 +1,5 @@ import { gte, lt } from 'semver'; + import { ElasticQueryBuilder } from '../query_builder'; import { ElasticsearchQuery } from '../types'; diff --git a/public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx b/public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx index 1b5e68db9f8..236f8e65c11 100644 --- a/public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx +++ b/public/app/plugins/datasource/elasticsearch/test-helpers/render.tsx @@ -1,8 +1,10 @@ -import React, { ComponentProps, ReactNode } from 'react'; import { render } from '@testing-library/react'; +import React, { ComponentProps, ReactNode } from 'react'; + import { getDefaultTimeRange } from '@grafana/data'; -import { ElasticDatasource } from '../datasource'; + import { ElasticsearchProvider } from '../components/QueryEditor/ElasticsearchQueryContext'; +import { ElasticDatasource } from '../datasource'; const defaultProviderProps = { datasource: {} as ElasticDatasource, diff --git a/public/app/plugins/datasource/elasticsearch/types.ts b/public/app/plugins/datasource/elasticsearch/types.ts index 8b0cd31af18..758acd48bf1 100644 --- a/public/app/plugins/datasource/elasticsearch/types.ts +++ b/public/app/plugins/datasource/elasticsearch/types.ts @@ -1,4 +1,5 @@ import { DataQuery, DataSourceJsonData } from '@grafana/data'; + import { BucketAggregation, BucketAggregationType, diff --git a/public/app/plugins/datasource/elasticsearch/utils.ts b/public/app/plugins/datasource/elasticsearch/utils.ts index e7969adb974..24280b2abb5 100644 --- a/public/app/plugins/datasource/elasticsearch/utils.ts +++ b/public/app/plugins/datasource/elasticsearch/utils.ts @@ -1,10 +1,11 @@ +import { valid } from 'semver'; + import { isMetricAggregationWithField, MetricAggregation, MetricAggregationWithInlineScript, } from './components/QueryEditor/MetricAggregationsEditor/aggregations'; import { metricAggregationConfig } from './components/QueryEditor/MetricAggregationsEditor/utils'; -import { valid } from 'semver'; export const describeMetric = (metric: MetricAggregation) => { if (!isMetricAggregationWithField(metric)) { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/datasource.ts index 1793aa8a397..b8a37e18fc2 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/datasource.ts @@ -16,9 +16,9 @@ export default function createMockDatasource(overrides?: DeepPartial }, getSubscriptions: jest.fn().mockResolvedValueOnce([]), defaultSubscriptionId: 'subscriptionId', - newGetMetricNamespaces: jest.fn().mockResolvedValueOnce([]), - newGetMetricNames: jest.fn().mockResolvedValueOnce([]), - newGetMetricMetadata: jest.fn().mockResolvedValueOnce({ + getMetricNamespaces: jest.fn().mockResolvedValueOnce([]), + getMetricNames: jest.fn().mockResolvedValueOnce([]), + getMetricMetadata: jest.fn().mockResolvedValueOnce({ primaryAggType: 'Average', supportedAggTypes: ['Average', 'Maximum', 'Minimum'], supportedTimeGrains: [], @@ -31,14 +31,6 @@ export default function createMockDatasource(overrides?: DeepPartial getResourceGroups: jest.fn().mockResolvedValueOnce([]), getMetricDefinitions: jest.fn().mockResolvedValueOnce([]), getResourceNames: jest.fn().mockResolvedValueOnce([]), - getMetricNamespaces: jest.fn().mockResolvedValueOnce([]), - getMetricNames: jest.fn().mockResolvedValueOnce([]), - getMetricMetadata: jest.fn().mockResolvedValueOnce({ - primaryAggType: 'Average', - supportedAggTypes: ['Average', 'Maximum', 'Minimum'], - supportedTimeGrains: [], - dimensions: [], - }), azureLogAnalyticsDatasource: { getKustoSchema: () => Promise.resolve(), diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/instanceSettings.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/instanceSettings.ts index 6857d6e0c5c..caf93081ac1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/instanceSettings.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/__mocks__/instanceSettings.ts @@ -1,4 +1,5 @@ import { DataSourcePluginMeta } from '@grafana/data'; + import { AzureDataSourceInstanceSettings } from '../types'; export const createMockInstanceSetttings = (): AzureDataSourceInstanceSettings => ({ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts index 9f3a7fd0903..6acfffb8630 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.test.ts @@ -5,6 +5,7 @@ import createMockQuery from '../__mocks__/query'; import { singleVariable } from '../__mocks__/variables'; import AzureMonitorDatasource from '../datasource'; import { AzureMonitorQuery, AzureQueryType, DatasourceValidationResult } from '../types'; + import FakeSchemaData from './__mocks__/schema'; import AzureLogAnalyticsDatasource from './azure_log_analytics_datasource'; @@ -137,10 +138,7 @@ describe('AzureLogAnalyticsDatasource', () => { it('should include template variables as global parameters', async () => { const result = await ctx.ds.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); - expect(result.globalParameters.map((f: { name: string }) => f.name)).toEqual([ - `$${singleVariable.name}`, - '$__timeFilter', - ]); + expect(result.globalParameters.map((f: { name: string }) => f.name)).toEqual([`$${singleVariable.name}`]); }); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts index 6f1ece771ba..192f84692bc 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/azure_log_analytics_datasource.ts @@ -1,3 +1,7 @@ +import { map } from 'lodash'; +import { from, Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + import { DataQueryRequest, DataQueryResponse, @@ -6,9 +10,6 @@ import { ScopedVars, } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; -import { map } from 'lodash'; -import { from, Observable } from 'rxjs'; -import { mergeMap } from 'rxjs/operators'; import { isGUIDish } from '../components/ResourcePicker/utils'; import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials'; @@ -21,6 +22,7 @@ import { DatasourceValidationResult, } from '../types'; import { interpolateVariable, routeNames } from '../utils/common'; + import ResponseParser, { transformMetadataToKustoSchema } from './response_parser'; interface AdhocQuery { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/response_parser.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/response_parser.ts index bce81329529..da97cbe4a45 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/response_parser.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_log_analytics/response_parser.ts @@ -1,6 +1,7 @@ -import { AnnotationEvent, dateTime, TimeSeries, VariableModel } from '@grafana/data'; import { concat, find, flattenDeep, forEach, get, map } from 'lodash'; +import { AnnotationEvent, dateTime, TimeSeries, VariableModel } from '@grafana/data'; + import { AzureLogsTableData, AzureLogsVariable } from '../types'; import { AzureLogAnalyticsMetadata } from '../types/logAnalyticsMetadata'; @@ -235,8 +236,8 @@ export function transformMetadataToKustoSchema( { name: 'timeColumn', type: 'System.String', - defaultValue: 'TimeGenerated', - cslDefaultValue: 'TimeGenerated', + defaultValue: '""', + cslDefaultValue: '""', }, ], }, @@ -290,15 +291,6 @@ export function transformMetadataToKustoSchema( }; }); - // It's not possible to define optional paramaters in Kusto - // and it's not possible to define the same function twice so - // we are defining $__timeFilter also as a parameter when used - // with no arguments as a workaround - globalParameters.push({ - name: `$__timeFilter`, - type: 'boolean', - }); - return { clusterType: 'Engine', cluster: { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts index a6cbb496067..96e1baa2cd9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts @@ -26,7 +26,7 @@ describe('AzureMonitorDatasource', () => { ctx.instanceSettings = { name: 'test', url: 'http://azuremonitor.com', - jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f', cloudName: 'azuremonitor' }, + jsonData: { subscriptionId: 'mock-subscription-id', cloudName: 'azuremonitor' }, } as unknown as DataSourceInstanceSettings; ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings); }); @@ -76,11 +76,11 @@ describe('AzureMonitorDatasource', () => { }); }); - describe('When performing newGetMetricNamespaces', () => { + describe('When performing getMetricNamespaces', () => { const response = { value: [ { - id: '/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1/providers/microsoft.insights/metricNamespaces/Azure.ApplicationInsights', + id: '/subscriptions/mock-subscription-id/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1/providers/microsoft.insights/metricNamespaces/Azure.ApplicationInsights', name: 'Azure.ApplicationInsights', type: 'Microsoft.Insights/metricNamespaces', classification: 'Custom', @@ -89,7 +89,7 @@ describe('AzureMonitorDatasource', () => { }, }, { - id: '/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1/providers/microsoft.insights/metricNamespaces/microsoft.insights-components', + id: '/subscriptions/mock-subscription-id/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1/providers/microsoft.insights/metricNamespaces/microsoft.insights-components', name: 'microsoft.insights-components', type: 'Microsoft.Insights/metricNamespaces', classification: 'Platform', @@ -102,7 +102,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + '/providers/microsoft.insights/components/resource1' + @@ -114,9 +114,10 @@ describe('AzureMonitorDatasource', () => { it('should return list of Metric Namspaces', () => { return ctx.ds.azureMonitorDatasource - .newGetMetricNamespaces( - '/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1' - ) + .getMetricNamespaces({ + resourceUri: + '/subscriptions/mock-subscription-id/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1', + }) .then((results: Array<{ text: string; value: string }>) => { expect(results.length).toEqual(2); expect(results[0].text).toEqual('Azure.ApplicationInsights'); @@ -127,7 +128,7 @@ describe('AzureMonitorDatasource', () => { }); }); - describe('When performing newGetMetricNames', () => { + describe('When performing getMetricNames', () => { const response = { value: [ { @@ -165,7 +166,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + '/providers/microsoft.insights/components/resource1' + @@ -177,10 +178,11 @@ describe('AzureMonitorDatasource', () => { it('should return list of Metric Definitions', () => { return ctx.ds.azureMonitorDatasource - .newGetMetricNames( - '/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1', - 'default' - ) + .getMetricNames({ + resourceUri: + '/subscriptions/mock-subscription-id/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1', + metricNamespace: 'default', + }) .then((results: Array<{ text: string; value: string }>) => { expect(results.length).toEqual(2); expect(results[0].text).toEqual('Used capacity'); @@ -191,7 +193,7 @@ describe('AzureMonitorDatasource', () => { }); }); - describe('When performing newGetMetricMetadata', () => { + describe('When performing getMetricMetadata', () => { const response = { value: [ { @@ -229,7 +231,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + '/providers/microsoft.insights/components/resource1' + @@ -241,11 +243,12 @@ describe('AzureMonitorDatasource', () => { it('should return Aggregation metadata for a Metric', () => { return ctx.ds.azureMonitorDatasource - .newGetMetricMetadata( - '/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1', - 'default', - 'UsedCapacity' - ) + .getMetricMetadata({ + resourceUri: + '/subscriptions/mock-subscription-id/resourceGroups/nodeapp/providers/microsoft.insights/components/resource1', + metricNamespace: 'default', + metricName: 'UsedCapacity', + }) .then((results) => { expect(results.primaryAggType).toEqual('Total'); expect(results.supportedAggTypes.length).toEqual(6); @@ -342,7 +345,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups'; expect(path).toBe(basePath + '/nodesapp/resources?api-version=2021-04-01'); return Promise.resolve(response); }); @@ -350,7 +353,7 @@ describe('AzureMonitorDatasource', () => { it('should return list of Metric Definitions with no duplicates and no unsupported namespaces', () => { return ctx.ds - .getMetricDefinitions('9935389e-9122-4ef9-95f9-1513dd24753f', 'nodesapp') + .getMetricDefinitions('mock-subscription-id', 'nodesapp') .then((results: Array<{ text: string; value: string }>) => { expect(results.length).toEqual(7); expect(results[0].text).toEqual('Network interface'); @@ -372,12 +375,12 @@ describe('AzureMonitorDatasource', () => { }); describe('When performing getResourceNames', () => { - let subscription = '9935389e-9122-4ef9-95f9-1513dd24753f'; + let subscription = 'mock-subscription-id'; let resourceGroup = 'nodeapp'; let metricDefinition = 'microsoft.insights/components'; beforeEach(() => { - subscription = '9935389e-9122-4ef9-95f9-1513dd24753f'; + subscription = 'mock-subscription-id'; resourceGroup = 'nodeapp'; metricDefinition = 'microsoft.insights/components'; }); @@ -552,7 +555,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + '/providers/microsoft.insights/components/resource1' + @@ -563,14 +566,14 @@ describe('AzureMonitorDatasource', () => { }); it('should return list of Metric Definitions', () => { - return ctx.ds - .getMetricNames( - '9935389e-9122-4ef9-95f9-1513dd24753f', - 'nodeapp', - 'microsoft.insights/components', - 'resource1', - 'default' - ) + return ctx.ds.azureMonitorDatasource + .getMetricNames({ + subscription: 'mock-subscription-id', + resourceGroup: 'nodeapp', + metricDefinition: 'microsoft.insights/components', + resourceName: 'resource1', + metricNamespace: 'default', + }) .then((results: Array<{ text: string; value: string }>) => { expect(results.length).toEqual(2); expect(results[0].text).toEqual('Used capacity'); @@ -619,7 +622,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + '/providers/microsoft.insights/components/resource1' + @@ -630,15 +633,15 @@ describe('AzureMonitorDatasource', () => { }); it('should return Aggregation metadata for a Metric', () => { - return ctx.ds - .getMetricMetadata( - '9935389e-9122-4ef9-95f9-1513dd24753f', - 'nodeapp', - 'microsoft.insights/components', - 'resource1', - 'default', - 'UsedCapacity' - ) + return ctx.ds.azureMonitorDatasource + .getMetricMetadata({ + subscription: 'mock-subscription-id', + resourceGroup: 'nodeapp', + metricDefinition: 'microsoft.insights/components', + resourceName: 'resource1', + metricNamespace: 'default', + metricName: 'UsedCapacity', + }) .then((results) => { expect(results.primaryAggType).toEqual('Total'); expect(results.supportedAggTypes.length).toEqual(6); @@ -688,7 +691,7 @@ describe('AzureMonitorDatasource', () => { beforeEach(() => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { - const basePath = 'azuremonitor/subscriptions/9935389e-9122-4ef9-95f9-1513dd24753f/resourceGroups/nodeapp'; + const basePath = 'azuremonitor/subscriptions/mock-subscription-id/resourceGroups/nodeapp'; const expected = basePath + '/providers/microsoft.insights/components/resource1' + @@ -699,15 +702,15 @@ describe('AzureMonitorDatasource', () => { }); it('should return dimensions for a Metric that has dimensions', () => { - return ctx.ds - .getMetricMetadata( - '9935389e-9122-4ef9-95f9-1513dd24753f', - 'nodeapp', - 'microsoft.insights/components', - 'resource1', - 'default', - 'Transactions' - ) + return ctx.ds.azureMonitorDatasource + .getMetricMetadata({ + subscription: 'mock-subscription-id', + resourceGroup: 'nodeapp', + metricDefinition: 'microsoft.insights/components', + resourceName: 'resource1', + metricNamespace: 'default', + metricName: 'Transactions', + }) .then((results: any) => { expect(results.dimensions).toMatchInlineSnapshot(` Array [ @@ -759,15 +762,15 @@ describe('AzureMonitorDatasource', () => { }); it('should return an empty array for a Metric that does not have dimensions', () => { - return ctx.ds - .getMetricMetadata( - '9935389e-9122-4ef9-95f9-1513dd24753f', - 'nodeapp', - 'microsoft.insights/components', - 'resource1', - 'default', - 'FreeCapacity' - ) + return ctx.ds.azureMonitorDatasource + .getMetricMetadata({ + subscription: 'mock-subscription-id', + resourceGroup: 'nodeapp', + metricDefinition: 'microsoft.insights/components', + resourceName: 'resource1', + metricNamespace: 'default', + metricName: 'FreeCapacity', + }) .then((results: any) => { expect(results.dimensions.length).toEqual(0); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts index b302061d483..fbc474e63ac 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts @@ -1,7 +1,8 @@ +import { filter, startsWith } from 'lodash'; + import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { filter, startsWith } from 'lodash'; import { resourceTypeDisplayNames } from '../azureMetadata'; import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials'; @@ -16,8 +17,12 @@ import { AzureMonitorResourceGroupsResponse, AzureQueryType, DatasourceValidationResult, + GetMetricNamespacesQuery, + GetMetricNamesQuery, + GetMetricMetadataQuery, } from '../types'; import { routeNames } from '../utils/common'; + import ResponseParser from './response_parser'; import SupportedNamespaces from './supported_namespaces'; import UrlBuilder from './url_builder'; @@ -236,102 +241,35 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { - return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName'); - }); - } - - newGetMetricNamespaces(resourceUri: string) { - const templateSrv = getTemplateSrv(); - const url = UrlBuilder.newBuildAzureMonitorGetMetricNamespacesUrl( - this.resourcePath, - templateSrv.replace(resourceUri), - this.apiPreviewVersion - ); - return this.getResource(url).then((result: AzureMonitorMetricNamespacesResponse) => { return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName'); }); } - getMetricNames( - subscriptionId: string, - resourceGroup: string, - metricDefinition: string, - resourceName: string, - metricNamespace: string - ) { + getMetricNames(query: GetMetricNamesQuery) { const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( this.resourcePath, - subscriptionId, - resourceGroup, - metricDefinition, - resourceName, - metricNamespace, - this.apiVersion + this.apiVersion, + this.replaceTemplateVariables(query) ); - - return this.getResource(url).then((result: any) => { - return ResponseParser.parseResponseValues(result, 'name.localizedValue', 'name.value'); - }); - } - - newGetMetricNames(resourceUri: string, metricNamespace: string) { - const templateSrv = getTemplateSrv(); - const url = UrlBuilder.newBuildAzureMonitorGetMetricNamesUrl( - this.resourcePath, - templateSrv.replace(resourceUri), - templateSrv.replace(metricNamespace), - this.apiVersion - ); - return this.getResource(url).then((result: AzureMonitorMetricNamesResponse) => { return ResponseParser.parseResponseValues(result, 'name.localizedValue', 'name.value'); }); } - getMetricMetadata( - subscriptionId: string, - resourceGroup: string, - metricDefinition: string, - resourceName: string, - metricNamespace: string, - metricName: string - ) { + getMetricMetadata(query: GetMetricMetadataQuery) { + const { metricName } = query; const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( this.resourcePath, - subscriptionId, - resourceGroup, - metricDefinition, - resourceName, - metricNamespace, - this.apiVersion + this.apiVersion, + this.replaceTemplateVariables(query) ); - - return this.getResource(url).then((result: any) => { - return ResponseParser.parseMetadata(result, metricName); - }); - } - - newGetMetricMetadata(resourceUri: string, metricNamespace: string, metricName: string) { - const templateSrv = getTemplateSrv(); - const url = UrlBuilder.newBuildAzureMonitorGetMetricNamesUrl( - this.resourcePath, - templateSrv.replace(resourceUri), - templateSrv.replace(metricNamespace), - this.apiVersion - ); - return this.getResource(url).then((result: AzureMonitorMetricsMetadataResponse) => { return ResponseParser.parseMetadata(result, metricName); }); @@ -398,4 +336,16 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend 0; } + + private replaceTemplateVariables(query: T) { + const templateSrv = getTemplateSrv(); + + const workingQuery: { [K in keyof T]: string } = { ...query }; + const keys = Object.keys(query) as Array; + keys.forEach((key) => { + workingQuery[key] = templateSrv.replace(workingQuery[key]); + }); + + return workingQuery; + } } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/response_parser.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/response_parser.ts index c2011e53967..640a127591e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/response_parser.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/response_parser.ts @@ -1,4 +1,5 @@ import { find, get } from 'lodash'; + import TimeGrainConverter from '../time_grain_converter'; import { AzureMonitorLocalizedValue, diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts index d744a229e0d..8bfcfec5435 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts @@ -10,7 +10,7 @@ export default class SupportedNamespaces { 'Microsoft.Cdn/cdnwebapplicationfirewallpolicies', 'Microsoft.Cdn/profiles', 'Microsoft.ClassicCompute/virtualMachines', - 'Microsoft.ClassicCompute/domainNames/slots/roles', + 'Microsoft.ClassicCompute/domainNames', 'Microsoft.CognitiveServices/accounts', 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets', @@ -96,7 +96,7 @@ export default class SupportedNamespaces { 'Microsoft.Cdn/cdnwebapplicationfirewallpolicies', 'Microsoft.Cdn/profiles', 'Microsoft.ClassicCompute/virtualMachines', - 'Microsoft.ClassicCompute/domainNames/slots/roles', + 'Microsoft.ClassicCompute/domainNames', 'Microsoft.CognitiveServices/accounts', 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets', @@ -153,7 +153,7 @@ export default class SupportedNamespaces { 'Microsoft.Cdn/cdnwebapplicationfirewallpolicies', 'Microsoft.Cdn/profiles', 'Microsoft.ClassicCompute/virtualMachines', - 'Microsoft.ClassicCompute/domainNames/slots/roles', + 'Microsoft.ClassicCompute/domainNames', 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets', 'Microsoft.DBforMySQL/servers', @@ -205,7 +205,7 @@ export default class SupportedNamespaces { 'Microsoft.Cdn/cdnwebapplicationfirewallpolicies', 'Microsoft.Cdn/profiles', 'Microsoft.ClassicCompute/virtualMachines', - 'Microsoft.ClassicCompute/domainNames/slots/roles', + 'Microsoft.ClassicCompute/domainNames', 'Microsoft.CognitiveServices/accounts', 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts index 4aa8b984548..ed61968eed7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts @@ -1,13 +1,19 @@ import UrlBuilder from './url_builder'; describe('AzureMonitorUrlBuilder', () => { + describe('buildResourceUri', () => { + it('builds a resource uri when the required properties are provided', () => { + expect(UrlBuilder.buildResourceUri('sub', 'group', 'Microsoft.NetApp/netAppAccounts', 'name')).toEqual( + '/subscriptions/sub/resourceGroups/group/providers/Microsoft.NetApp/netAppAccounts/name' + ); + }); + }); + describe('when a resource uri is provided', () => { it('builds a getMetricNamesnamespace url', () => { - const url = UrlBuilder.newBuildAzureMonitorGetMetricNamespacesUrl( - '', - '/subscriptions/sub/resource-uri/resource', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { + resourceUri: '/subscriptions/sub/resource-uri/resource', + }); expect(url).toBe( '/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' ); @@ -16,12 +22,10 @@ describe('AzureMonitorUrlBuilder', () => { describe('when a resource uri and metric namespace is provided', () => { it('builds a getMetricNames url', () => { - const url = UrlBuilder.newBuildAzureMonitorGetMetricNamesUrl( - '', - '/subscriptions/sub/resource-uri/resource', - 'Microsoft.Sql/servers', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + resourceUri: '/subscriptions/sub/resource-uri/resource', + metricNamespace: 'Microsoft.Sql/servers', + }); expect(url).toBe( '/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=Microsoft.Sql%2Fservers' ); @@ -31,14 +35,12 @@ describe('AzureMonitorUrlBuilder', () => { describe('Legacy query object', () => { describe('when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes', () => { it('should build the getMetricNamespaces url in the even longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', - 'rn1/rn2/rn3', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', + resourceName: 'rn1/rn2/rn3', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/' + 'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' @@ -48,14 +50,12 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers/databases', () => { it('should build the getMetricNamespaces url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Sql/servers/databases', - 'rn1/rn2', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers/databases', + resourceName: 'rn1/rn2', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' + 'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' @@ -65,14 +65,12 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers', () => { it('should build the getMetricNamespaces url in the shorter format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Sql/servers', - 'rn', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamespacesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers', + resourceName: 'rn', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' + 'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' @@ -82,15 +80,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.NetApp/netAppAccounts/capacityPools/volumes and the metricNamespace is default', () => { it('should build the getMetricNames url in the even longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', - 'rn1/rn2/rn3', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.NetApp/netAppAccounts/capacityPools/volumes', + resourceName: 'rn1/rn2/rn3', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -100,15 +96,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers/databases and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Sql/servers/databases', - 'rn1/rn2', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers/databases', + resourceName: 'rn1/rn2', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -118,15 +112,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Sql/servers and the metricNamespace is default', () => { it('should build the getMetricNames url in the shorter format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Sql/servers', - 'rn', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Sql/servers', + resourceName: 'rn', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -136,15 +128,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/blobServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Storage/storageAccounts/blobServices', - 'rn1/default', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/blobServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -154,15 +144,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/fileServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Storage/storageAccounts/fileServices', - 'rn1/default', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/fileServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -172,15 +160,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/tableServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Storage/storageAccounts/tableServices', - 'rn1/default', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/tableServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/tableServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' @@ -190,15 +176,13 @@ describe('AzureMonitorUrlBuilder', () => { describe('when metric definition is Microsoft.Storage/storageAccounts/queueServices and the metricNamespace is default', () => { it('should build the getMetricNames url in the longer format', () => { - const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl( - '', - 'sub1', - 'rg', - 'Microsoft.Storage/storageAccounts/queueServices', - 'rn1/default', - 'default', - '2017-05-01-preview' - ); + const url = UrlBuilder.buildAzureMonitorGetMetricNamesUrl('', '2017-05-01-preview', { + subscription: 'sub1', + resourceGroup: 'rg', + metricDefinition: 'Microsoft.Storage/storageAccounts/queueServices', + resourceName: 'rn1/default', + metricNamespace: 'default', + }); expect(url).toBe( '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/queueServices/default/' + 'providers/microsoft.insights/metricdefinitions?api-version=2017-05-01-preview&metricnamespace=default' diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts index 1d18c89a09d..7684a543e07 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts @@ -1,58 +1,47 @@ +import { GetMetricNamespacesQuery, GetMetricNamesQuery } from '../types'; + export default class UrlBuilder { - static buildAzureMonitorGetMetricNamespacesUrl( - baseUrl: string, + static buildResourceUri( subscriptionId: string, resourceGroup: string, metricDefinition: string, - resourceName: string, - apiVersion: string + resourceName: string ) { const metricDefinitionArray = metricDefinition.split('/'); const resourceNameArray = resourceName.split('/'); const provider = metricDefinitionArray.shift(); - const urlArray = [baseUrl, 'subscriptions', subscriptionId, 'resourceGroups', resourceGroup, 'providers', provider]; + const urlArray = ['/subscriptions', subscriptionId, 'resourceGroups', resourceGroup, 'providers', provider]; for (const i in metricDefinitionArray) { urlArray.push(metricDefinitionArray[i]); urlArray.push(resourceNameArray[i]); } - const urlPrefix = urlArray.join('/'); - return `${urlPrefix}/providers/microsoft.insights/metricNamespaces?api-version=${apiVersion}`; + return urlArray.join('/'); } - static buildAzureMonitorGetMetricNamesUrl( - baseUrl: string, - subscriptionId: string, - resourceGroup: string, - metricDefinition: string, - resourceName: string, - metricNamespace: string, - apiVersion: string - ) { - const metricDefinitionArray = metricDefinition.split('/'); - const resourceNameArray = resourceName.split('/'); - const provider = metricDefinitionArray.shift(); - const urlArray = [baseUrl, 'subscriptions', subscriptionId, 'resourceGroups', resourceGroup, 'providers', provider]; - for (const i in metricDefinitionArray) { - urlArray.push(metricDefinitionArray[i]); - urlArray.push(resourceNameArray[i]); + static buildAzureMonitorGetMetricNamespacesUrl(baseUrl: string, apiVersion: string, query: GetMetricNamespacesQuery) { + let resourceUri: string; + + if ('resourceUri' in query) { + resourceUri = query.resourceUri; + } else { + const { subscription, resourceGroup, metricDefinition, resourceName } = query; + resourceUri = UrlBuilder.buildResourceUri(subscription, resourceGroup, metricDefinition, resourceName); } - const urlPrefix = urlArray.join('/'); - return ( - `${urlPrefix}/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}` + - `&metricnamespace=${encodeURIComponent(metricNamespace)}` - ); - } - static newBuildAzureMonitorGetMetricNamespacesUrl(baseUrl: string, resourceUri: string, apiVersion: string) { return `${baseUrl}${resourceUri}/providers/microsoft.insights/metricNamespaces?api-version=${apiVersion}`; } - static newBuildAzureMonitorGetMetricNamesUrl( - baseUrl: string, - resourceUri: string, - metricNamespace: string, - apiVersion: string - ) { + static buildAzureMonitorGetMetricNamesUrl(baseUrl: string, apiVersion: string, query: GetMetricNamesQuery) { + let resourceUri: string; + const { metricNamespace } = query; + + if ('resourceUri' in query) { + resourceUri = query.resourceUri; + } else { + const { subscription, resourceGroup, metricDefinition, resourceName } = query; + resourceUri = UrlBuilder.buildResourceUri(subscription, resourceGroup, metricDefinition, resourceName); + } + return ( `${baseUrl}${resourceUri}/providers/microsoft.insights/metricdefinitions?api-version=${apiVersion}` + `&metricnamespace=${encodeURIComponent(metricNamespace)}` diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.test.ts index d5ac3c2ca60..a0e3f2514aa 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.test.ts @@ -1,10 +1,12 @@ -import { TemplateSrv } from 'app/features/templating/template_srv'; import { backendSrv } from 'app/core/services/backend_srv'; -import AzureResourceGraphDatasource from './azure_resource_graph_datasource'; -import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables'; -import { AzureQueryType } from '../types'; -import AzureMonitorDatasource from '../datasource'; +import { TemplateSrv } from 'app/features/templating/template_srv'; + import createMockQuery from '../__mocks__/query'; +import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables'; +import AzureMonitorDatasource from '../datasource'; +import { AzureQueryType } from '../types'; + +import AzureResourceGraphDatasource from './azure_resource_graph_datasource'; const templateSrv = new TemplateSrv({ getVariables: () => [subscriptionsVariable, singleVariable, multiVariable], diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.ts index e768b9d2d77..5543b2baee7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_resource_graph/azure_resource_graph_datasource.ts @@ -1,8 +1,10 @@ // eslint-disable-next-line lodash/import-scope import _ from 'lodash'; -import { AzureMonitorQuery, AzureDataSourceJsonData, AzureQueryType } from '../types'; + import { ScopedVars } from '@grafana/data'; import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime'; + +import { AzureMonitorQuery, AzureDataSourceJsonData, AzureQueryType } from '../types'; import { interpolateVariable } from '../utils/common'; export default class AzureResourceGraphDatasource extends DataSourceWithBackend< diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/ArgQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/ArgQueryEditor.tsx index 490af50d0b9..cfc0dbba3f4 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/ArgQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/ArgQueryEditor.tsx @@ -1,8 +1,11 @@ import React, { useEffect, useState, useRef } from 'react'; -import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; -import Datasource from '../../datasource'; + import { InlineFieldRow } from '@grafana/ui'; + +import Datasource from '../../datasource'; +import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; import SubscriptionField from '../SubscriptionField'; + import QueryField from './QueryField'; interface LogsQueryEditorProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/QueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/QueryField.tsx index 9f05f8a16ee..9ce83b50797 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/QueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ArgQueryEditor/QueryField.tsx @@ -1,5 +1,7 @@ -import { CodeEditor } from '@grafana/ui'; import React, { useCallback } from 'react'; + +import { CodeEditor } from '@grafana/ui'; + import { AzureQueryEditorFieldProps } from '../../types'; const QueryField: React.FC = ({ query, onQueryChange }) => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx index 3afa70a9763..5b4f4f0e061 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + import AzureCredentialsForm, { Props } from './AzureCredentialsForm'; const setup = (propsFunc?: (props: Props) => Props) => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx index 2d7429cbdc0..caebcda67dc 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx @@ -1,8 +1,10 @@ import React, { ChangeEvent, FunctionComponent, useEffect, useReducer, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { InlineFormLabel, LegacyForms, Button, Select } from '@grafana/ui'; -import { AzureAuthType, AzureCredentials } from '../types'; + import { isCredentialsComplete } from '../credentials'; +import { AzureAuthType, AzureCredentials } from '../types'; const { Input } = LegacyForms; export interface Props { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.test.tsx index 688a560d000..39c29eb5098 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; + import ConfigEditor from './ConfigEditor'; describe('AppInsights ConfigEditor', () => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.tsx index 07bf6c63827..685c313c390 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ConfigEditor.tsx @@ -1,15 +1,17 @@ +import React, { PureComponent } from 'react'; + import { DataSourcePluginOptionsEditorProps, SelectableValue, updateDatasourcePluginOption } from '@grafana/data'; import { getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { Alert } from '@grafana/ui'; -import React, { PureComponent } from 'react'; import ResponseParser from '../azure_monitor/response_parser'; import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types'; import { routeNames } from '../utils/common'; + +import { MonitorConfig } from './MonitorConfig'; import { AnalyticsConfig } from './deprecated/components/AnalyticsConfig'; import { InsightsConfig } from './deprecated/components/InsightsConfig'; import { gtGrafana9, isAppInsightsConfigured } from './deprecated/utils'; -import { MonitorConfig } from './MonitorConfig'; export type Props = DataSourcePluginOptionsEditorProps; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Field.tsx index 6105bbc02eb..a40992d93a6 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Field.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Field.tsx @@ -1,5 +1,6 @@ -import { InlineField } from '@grafana/ui'; import React from 'react'; + +import { InlineField } from '@grafana/ui'; import { Props as InlineFieldProps } from '@grafana/ui/src/components/Forms/InlineField'; const DEFAULT_LABEL_WIDTH = 18; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/FormatAsField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/FormatAsField.tsx index 727bc5420e2..75c86f58680 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/FormatAsField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/FormatAsField.tsx @@ -1,8 +1,11 @@ import React, { useCallback, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps } from '../../types'; import { Field } from '../Field'; + import { setFormatAs } from './setQueryValue'; const FORMAT_OPTIONS: Array> = [ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx index 4e63ac97ed5..d81ac96920b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx @@ -1,13 +1,16 @@ import React from 'react'; -import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; -import Datasource from '../../datasource'; + import { Alert, InlineFieldRow } from '@grafana/ui'; -import { ResourceRowType } from '../ResourcePicker/types'; + +import Datasource from '../../datasource'; +import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; import ResourceField from '../ResourceField'; -import QueryField from './QueryField'; +import { ResourceRowType } from '../ResourcePicker/types'; + import FormatAsField from './FormatAsField'; -import useMigrations from './useMigrations'; +import QueryField from './QueryField'; import { setResource } from './setQueryValue'; +import useMigrations from './useMigrations'; interface LogsQueryEditorProps { query: AzureMonitorQuery; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/QueryField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/QueryField.tsx index b435ca4955f..9c32ea61699 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/QueryField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/QueryField.tsx @@ -1,8 +1,10 @@ -import { CodeEditor, Monaco, MonacoEditor } from '@grafana/ui'; -import { Deferred } from 'app/core/utils/deferred'; import React, { useCallback, useEffect, useRef } from 'react'; +import { CodeEditor, Monaco, MonacoEditor } from '@grafana/ui'; +import { Deferred } from 'app/core/utils/deferred'; + import { AzureQueryEditorFieldProps } from '../../types'; + import { setKustoQuery } from './setQueryValue'; interface MonacoPromise { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/useMigrations.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/useMigrations.ts index f91af739520..33ba224000e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/useMigrations.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/useMigrations.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; -import { AzureMonitorQuery } from '../../types'; + import Datasource from '../../datasource'; +import { AzureMonitorQuery } from '../../types'; import { isGUIDish } from '../ResourcePicker/utils'; async function migrateWorkspaceQueryToResourceQuery( diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx index 3d921b7fc43..abdc1bda308 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/AggregationField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setAggregation } from './setQueryValue'; interface AggregationFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx index c53138d86a5..664c9b31f61 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/DimensionFields.tsx @@ -1,8 +1,10 @@ import React, { useMemo } from 'react'; + import { Button, Select, Input, HorizontalGroup, VerticalGroup, InlineLabel } from '@grafana/ui'; -import { Field } from '../Field'; import { AzureMetricDimension, AzureMonitorOption, AzureQueryEditorFieldProps } from '../../types'; +import { Field } from '../Field'; + import { appendDimensionFilter, removeDimensionFilter, setDimensionFilterValue } from './setQueryValue'; interface DimensionFieldsProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx index 4b8522819f6..31ddc067e9b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/LegendFormatField.tsx @@ -1,8 +1,10 @@ import React, { useCallback, useState } from 'react'; + import { Input } from '@grafana/ui'; -import { Field } from '../Field'; import { AzureQueryEditorFieldProps } from '../../types'; +import { Field } from '../Field'; + import { setLegendAlias } from './setQueryValue'; const LegendFormatField: React.FC = ({ onQueryChange, query }) => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx index f7ec6d3b2b1..5cd903b7875 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNameField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setMetricName } from './setQueryValue'; interface MetricNameProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.test.tsx index 7fbd74de615..0f4ccda07bd 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import MetricNamespaceField from './MetricNamespaceField'; -import createMockQuery from '../../__mocks__/query'; +import React from 'react'; + import createMockDatasource from '../../__mocks__/datasource'; +import createMockQuery from '../../__mocks__/query'; import { AzureMonitorOption } from '../../types'; +import MetricNamespaceField from './MetricNamespaceField'; + const props = { metricNamespaces: [], query: createMockQuery(), diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx index 579d34dff33..8dff6139894 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricNamespaceField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setMetricNamespace } from './setQueryValue'; interface MetricNamespaceFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx index 0d013ea943c..fde54f35f09 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -1,11 +1,12 @@ -import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; + import { selectOptionInTest } from '@grafana/ui'; -import MetricsQueryEditor from './MetricsQueryEditor'; - -import createMockQuery from '../../__mocks__/query'; import createMockDatasource from '../../__mocks__/datasource'; +import createMockQuery from '../../__mocks__/query'; + +import MetricsQueryEditor from './MetricsQueryEditor'; const variableOptionGroup = { label: 'Template variables', @@ -79,7 +80,7 @@ describe('Azure Monitor QueryEditor', () => { const mockDatasource = createMockDatasource(); const onChange = jest.fn(); const mockQuery = createMockQuery(); - mockDatasource.getMetricNames = jest.fn().mockResolvedValue([ + mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([ { value: 'metric-a', text: 'Metric A', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx index bfbcd9091ff..d2a2adedab8 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -1,19 +1,21 @@ import React from 'react'; +import { InlineFieldRow } from '@grafana/ui'; + import Datasource from '../../datasource'; import { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types'; import SubscriptionField from '../SubscriptionField'; + +import AggregationField from './AggregationField'; +import DimensionFields from './DimensionFields'; +import LegendFormatField from './LegendFormatField'; +import MetricNameField from './MetricNameField'; import MetricNamespaceField from './MetricNamespaceField'; -import ResourceTypeField from './ResourceTypeField'; import ResourceGroupsField from './ResourceGroupsField'; import ResourceNameField from './ResourceNameField'; -import MetricNameField from './MetricNameField'; -import AggregationField from './AggregationField'; +import ResourceTypeField from './ResourceTypeField'; import TimeGrainField from './TimeGrainField'; -import DimensionFields from './DimensionFields'; import TopField from './TopField'; -import LegendFormatField from './LegendFormatField'; -import { InlineFieldRow } from '@grafana/ui'; import { useMetricNames, useMetricNamespaces, diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx index 8b052c9d4c8..83bf5c6d7ae 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceGroupsField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setResourceGroup } from './setQueryValue'; interface ResourceGroupsFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx index 63c3c5780b2..9736ff704f7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceNameField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setResourceName } from './setQueryValue'; interface ResourceNameFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx index f7abefb9cdb..ed7671ad2af 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/ResourceTypeField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setResourceType } from './setQueryValue'; interface NamespaceFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx index b3cd7cb7914..d48fb0e2bb8 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TimeGrainField.tsx @@ -1,10 +1,12 @@ import React, { useCallback, useMemo } from 'react'; -import { Select } from '@grafana/ui'; -import { SelectableValue } from '@grafana/data'; -import { Field } from '../Field'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import TimegrainConverter from '../../time_grain_converter'; import { AzureQueryEditorFieldProps, AzureMonitorOption } from '../../types'; +import { Field } from '../Field'; + import { setTimeGrain } from './setQueryValue'; interface TimeGrainFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx index d384a42f0be..b13a41f2517 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/TopField.tsx @@ -1,8 +1,10 @@ import React, { useCallback, useState } from 'react'; + import { Input } from '@grafana/ui'; -import { Field } from '../Field'; import { AzureQueryEditorFieldProps } from '../../types'; +import { Field } from '../Field'; + import { setTop } from './setQueryValue'; const TopField: React.FC = ({ onQueryChange, query }) => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts index 2b25f47ad2c..1c7f84d40da 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.test.ts @@ -3,6 +3,7 @@ import { renderHook } from '@testing-library/react-hooks'; import createMockDatasource from '../../__mocks__/datasource'; import Datasource from '../../datasource'; import { AzureMetricQuery, AzureMonitorOption, AzureMonitorQuery, AzureQueryType } from '../../types'; + import { DataHook, updateSubscriptions, @@ -247,11 +248,11 @@ describe('AzureMonitor: metrics dataHooks', () => { .fn() .mockResolvedValue([opt('Web server', 'web-server'), opt('Job server', 'job-server')]); - datasource.getMetricNames = jest + datasource.azureMonitorDatasource.getMetricNames = jest .fn() .mockResolvedValue([opt('Percentage CPU', 'percentage-cpu'), opt('Free memory', 'free-memory')]); - datasource.getMetricNamespaces = jest + datasource.azureMonitorDatasource.getMetricNamespaces = jest .fn() .mockResolvedValue([opt('Compute Virtual Machine', 'azure/vmc'), opt('Database NS', 'azure/dbns')]); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts index c340e9929bb..146bab298da 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/dataHooks.ts @@ -4,6 +4,7 @@ import Datasource from '../../datasource'; import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; import { hasOption, toOption } from '../../utils/common'; import { useAsyncState } from '../../utils/useAsyncState'; + import { setMetricNamespace, setSubscriptionID } from './setQueryValue'; export interface MetricMetadata { @@ -153,7 +154,12 @@ export const useMetricNamespaces: DataHook = (query, datasource, onChange, setEr return; } - const results = await datasource.getMetricNamespaces(subscription, resourceGroup, metricDefinition, resourceName); + const results = await datasource.azureMonitorDatasource.getMetricNamespaces({ + subscription, + resourceGroup, + metricDefinition, + resourceName, + }); const options = formatOptions(results, metricNamespace); // Do some cleanup of the query state if need be @@ -180,13 +186,13 @@ export const useMetricNames: DataHook = (query, datasource, onChange, setError) return; } - const results = await datasource.getMetricNames( + const results = await datasource.azureMonitorDatasource.getMetricNames({ subscription, resourceGroup, metricDefinition, resourceName, - metricNamespace - ); + metricNamespace, + }); const options = formatOptions(results, metricName); @@ -217,8 +223,8 @@ export const useMetricMetadata = (query: AzureMonitorQuery, datasource: Datasour return; } - datasource - .getMetricMetadata(subscription, resourceGroup, metricDefinition, resourceName, metricNamespace, metricName) + datasource.azureMonitorDatasource + .getMetricMetadata({ subscription, resourceGroup, metricDefinition, resourceName, metricNamespace, metricName }) .then((metadata) => { // TODO: Move the aggregationTypes and timeGrain defaults into `getMetricMetadata` const aggregations = (metadata.supportedAggTypes || [metadata.primaryAggType]).map((v) => ({ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx index aeaf1514c73..3f92cea8f1c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MonitorConfig.tsx @@ -1,9 +1,12 @@ import React, { FunctionComponent, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { AzureCredentialsForm } from './AzureCredentialsForm'; -import { AzureDataSourceSettings, AzureCredentials } from '../types'; + import { getCredentials, updateCredentials } from '../credentials'; +import { AzureDataSourceSettings, AzureCredentials } from '../types'; + +import { AzureCredentialsForm } from './AzureCredentialsForm'; const azureClouds = [ { value: 'azuremonitor', label: 'Azure' }, diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx index 792d3b76123..4a0400bec9e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -1,11 +1,11 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { selectOptionInTest } from '@grafana/ui'; -import MetricsQueryEditor from './MetricsQueryEditor'; -import createMockQuery from '../../__mocks__/query'; import createMockDatasource from '../../__mocks__/datasource'; +import createMockQuery from '../../__mocks__/query'; import createMockResourcePickerData from '../../__mocks__/resourcePickerData'; import { createMockResourceGroupsBySubscription, @@ -13,6 +13,8 @@ import { mockResourcesByResourceGroup, } from '../../__mocks__/resourcePickerRows'; +import MetricsQueryEditor from './MetricsQueryEditor'; + const variableOptionGroup = { label: 'Template variables', options: [], @@ -83,9 +85,9 @@ describe('MetricsQueryEditor', () => { const checkbox = await screen.findByLabelText('web-server'); expect(checkbox).toBeInTheDocument(); expect(checkbox).not.toBeChecked(); - userEvent.click(checkbox); + await userEvent.click(checkbox); expect(checkbox).toBeChecked(); - userEvent.click(await screen.findByRole('button', { name: 'Apply' })); + await userEvent.click(await screen.findByRole('button', { name: 'Apply' })); expect(onChange).toBeCalledTimes(1); expect(onChange).toBeCalledWith( @@ -136,9 +138,9 @@ describe('MetricsQueryEditor', () => { const checkbox = await screen.findByLabelText('db-server'); expect(checkbox).toBeInTheDocument(); expect(checkbox).not.toBeChecked(); - userEvent.click(checkbox); + await userEvent.click(checkbox); expect(checkbox).toBeChecked(); - userEvent.click(await screen.findByRole('button', { name: 'Apply' })); + await userEvent.click(await screen.findByRole('button', { name: 'Apply' })); expect(onChange).toBeCalledTimes(1); expect(onChange).toBeCalledWith( @@ -160,7 +162,7 @@ describe('MetricsQueryEditor', () => { const mockDatasource = createMockDatasource({ resourcePickerData }); const onChange = jest.fn(); const mockQuery = createMockQuery(); - mockDatasource.azureMonitorDatasource.newGetMetricNames = jest.fn().mockResolvedValue([ + mockDatasource.azureMonitorDatasource.getMetricNames = jest.fn().mockResolvedValue([ { value: 'metric-a', text: 'Metric A', diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.tsx index 97e6bb3b0e1..7b77f645cea 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/MetricsQueryEditor.tsx @@ -1,18 +1,21 @@ import React from 'react'; + import { InlineFieldRow } from '@grafana/ui'; + +import type Datasource from '../../datasource'; +import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types'; import AggregationField from '../MetricsQueryEditor/AggregationField'; +import DimensionFields from '../MetricsQueryEditor/DimensionFields'; +import LegendFormatField from '../MetricsQueryEditor/LegendFormatField'; import MetricNameField from '../MetricsQueryEditor/MetricNameField'; import MetricNamespaceField from '../MetricsQueryEditor/MetricNamespaceField'; import TimeGrainField from '../MetricsQueryEditor/TimeGrainField'; -import DimensionFields from '../MetricsQueryEditor/DimensionFields'; import TopField from '../MetricsQueryEditor/TopField'; -import LegendFormatField from '../MetricsQueryEditor/LegendFormatField'; +import { setResource } from '../MetricsQueryEditor/setQueryValue'; import ResourceField from '../ResourceField'; import { ResourceRowType } from '../ResourcePicker/types'; -import type Datasource from '../../datasource'; -import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types'; + import { useMetricNames, useMetricNamespaces, useMetricMetadata } from './dataHooks'; -import { setResource } from '../MetricsQueryEditor/setQueryValue'; interface MetricsQueryEditorProps { query: AzureMonitorQuery; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.test.ts index 7a784885c91..fc27d5077f3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.test.ts @@ -4,6 +4,7 @@ import createMockDatasource from '../../__mocks__/datasource'; import Datasource from '../../datasource'; import { AzureMetricQuery, AzureMonitorOption, AzureMonitorQuery, AzureQueryType } from '../../types'; import { DataHook } from '../MetricsQueryEditor/dataHooks'; + import { useMetricNames, useMetricNamespaces } from './dataHooks'; const WAIT_OPTIONS = { @@ -130,11 +131,11 @@ describe('AzureMonitor: metrics dataHooks', () => { .fn() .mockResolvedValue([opt('Web server', 'web-server'), opt('Job server', 'job-server')]); - datasource.azureMonitorDatasource.newGetMetricNames = jest + datasource.azureMonitorDatasource.getMetricNames = jest .fn() .mockResolvedValue([opt('Percentage CPU', 'percentage-cpu'), opt('Free memory', 'free-memory')]); - datasource.azureMonitorDatasource.newGetMetricNamespaces = jest + datasource.azureMonitorDatasource.getMetricNamespaces = jest .fn() .mockResolvedValue([opt('Compute Virtual Machine', 'azure/vmc'), opt('Database NS', 'azure/dbns')]); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts index a8c11ebd38b..e66a83b49e0 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/NewMetricsQueryEditor/dataHooks.ts @@ -29,7 +29,7 @@ export const useMetricNamespaces: DataHook = (query, datasource, onChange, setEr return; } - const results = await datasource.azureMonitorDatasource.newGetMetricNamespaces(resourceUri); + const results = await datasource.azureMonitorDatasource.getMetricNamespaces({ resourceUri }); const options = formatOptions(results, metricNamespace); // Do some cleanup of the query state if need be @@ -55,7 +55,7 @@ export const useMetricNames: DataHook = (query, datasource, onChange, setError) return; } - const results = await datasource.azureMonitorDatasource.newGetMetricNames(resourceUri, metricNamespace); + const results = await datasource.azureMonitorDatasource.getMetricNames({ resourceUri, metricNamespace }); const options = formatOptions(results, metricName); return options; @@ -87,7 +87,7 @@ export const useMetricMetadata = (query: AzureMonitorQuery, datasource: Datasour } datasource.azureMonitorDatasource - .newGetMetricMetadata(resourceUri, metricNamespace, metricName) + .getMetricMetadata({ resourceUri, metricNamespace, metricName }) .then((metadata) => { // TODO: Move the aggregationTypes and timeGrain defaults into `getMetricMetadata` const aggregations = (metadata.supportedAggTypes || [metadata.primaryAggType]).map((v) => ({ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.test.tsx index 891327b463d..1ef54499861 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.test.tsx @@ -1,13 +1,15 @@ -import * as ui from '@grafana/ui'; -import { config } from '@grafana/runtime'; import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import selectEvent from 'react-select-event'; +import { config } from '@grafana/runtime'; +import * as ui from '@grafana/ui'; + import createMockDatasource from '../../__mocks__/datasource'; import { invalidNamespaceError } from '../../__mocks__/errors'; import createMockQuery from '../../__mocks__/query'; import { AzureQueryType, DeprecatedAzureQueryType } from '../../types'; + import QueryEditor from './QueryEditor'; // Have to mock CodeEditor because it doesnt seem to work in tests??? diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.tsx index dbc618bc6bf..f0d168ef5c6 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor.tsx @@ -1,10 +1,10 @@ -import { QueryEditorProps } from '@grafana/data'; -import { Alert } from '@grafana/ui'; -import { config } from '@grafana/runtime'; - import { debounce } from 'lodash'; import React, { useCallback, useMemo } from 'react'; +import { QueryEditorProps } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Alert } from '@grafana/ui'; + import AzureMonitorDatasource from '../../datasource'; import { AzureDataSourceJsonData, @@ -16,13 +16,14 @@ import { } from '../../types'; import useLastError from '../../utils/useLastError'; import ArgQueryEditor from '../ArgQueryEditor'; -import ApplicationInsightsEditor from '../deprecated/components/ApplicationInsightsEditor'; -import InsightsAnalyticsEditor from '../deprecated/components/InsightsAnalyticsEditor'; -import { gtGrafana9 } from '../deprecated/utils'; import LogsQueryEditor from '../LogsQueryEditor'; import MetricsQueryEditor from '../MetricsQueryEditor'; import NewMetricsQueryEditor from '../NewMetricsQueryEditor/MetricsQueryEditor'; import { Space } from '../Space'; +import ApplicationInsightsEditor from '../deprecated/components/ApplicationInsightsEditor'; +import InsightsAnalyticsEditor from '../deprecated/components/InsightsAnalyticsEditor'; +import { gtGrafana9 } from '../deprecated/utils'; + import QueryTypeField from './QueryTypeField'; import usePreparedQuery from './usePreparedQuery'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx index d6bdd088398..0ea008631cd 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryTypeField.tsx @@ -1,10 +1,11 @@ -import { SelectableValue } from '@grafana/data'; -import { Select } from '@grafana/ui'; import React, { useCallback, useState } from 'react'; +import { SelectableValue } from '@grafana/data'; +import { Select } from '@grafana/ui'; + import { AzureMonitorQuery, AzureQueryType, DeprecatedAzureQueryType } from '../../types'; -import { gtGrafana9 } from '../deprecated/utils'; import { Field } from '../Field'; +import { gtGrafana9 } from '../deprecated/utils'; interface QueryTypeFieldProps { query: AzureMonitorQuery; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts index 4a33d06a5de..560d734cbf6 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/usePreparedQuery.ts @@ -1,7 +1,8 @@ -import { useEffect, useMemo } from 'react'; -import { defaults } from 'lodash'; -import { AzureMonitorQuery, AzureQueryType } from '../../types'; import deepEqual from 'fast-deep-equal'; +import { defaults } from 'lodash'; +import { useEffect, useMemo } from 'react'; + +import { AzureMonitorQuery, AzureQueryType } from '../../types'; import migrateQuery from '../../utils/migrateQuery'; const DEFAULT_QUERY = { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourceField/ResourceField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourceField/ResourceField.tsx index fedcda00464..2a2bb98b879 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourceField/ResourceField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourceField/ResourceField.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; +import React, { useCallback, useEffect, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Button, Icon, Modal, useStyles2 } from '@grafana/ui'; -import React, { useCallback, useEffect, useState } from 'react'; import Datasource from '../../datasource'; import { AzureQueryEditorFieldProps, AzureMonitorQuery, AzureResourceSummaryItem } from '../../types'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/EntryIcon.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/EntryIcon.tsx index c24861fc235..b37b3583c25 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/EntryIcon.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/EntryIcon.tsx @@ -1,6 +1,7 @@ -import { Icon } from '@grafana/ui'; import React from 'react'; +import { Icon } from '@grafana/ui'; + import { ResourceRow, ResourceRowType } from './types'; interface EntryIconProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.test.tsx index c0df55eb04b..7fab7fcc12e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; + import { NestedEntry } from './NestedEntry'; import { ResourceRowType } from './types'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx index dbdabcac023..4dba5bf0666 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx @@ -1,8 +1,10 @@ import { cx } from '@emotion/css'; -import { Checkbox, IconButton, useStyles2, useTheme2 } from '@grafana/ui'; import React, { useCallback, useEffect } from 'react'; +import { Checkbox, IconButton, useStyles2, useTheme2 } from '@grafana/ui'; + import { Space } from '../Space'; + import { EntryIcon } from './EntryIcon'; import getStyles from './styles'; import { ResourceRow } from './types'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedResourceTable.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedResourceTable.tsx index 267cfaebc7a..29bf6f20ef5 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedResourceTable.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedResourceTable.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import { cx } from '@emotion/css'; +import React from 'react'; import { useStyles2 } from '@grafana/ui'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx index be4955a0604..62583286bf1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx @@ -1,7 +1,8 @@ import { cx } from '@emotion/css'; -import { FadeTransition, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import React, { useEffect, useState } from 'react'; +import { FadeTransition, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; + import { NestedEntry } from './NestedEntry'; import NestedRows from './NestedRows'; import getStyles from './styles'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx index b66d622aa57..df223499781 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import NestedRow from './NestedRow'; +import NestedRow from './NestedRow'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; interface NestedRowsProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx index 7b6a3881623..26196935b62 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx @@ -2,15 +2,17 @@ import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import ResourcePicker from '.'; import createMockResourcePickerData from '../../__mocks__/resourcePickerData'; import { createMockResourceGroupsBySubscription, createMockSubscriptions, mockResourcesByResourceGroup, } from '../../__mocks__/resourcePickerRows'; + import { ResourceRowType } from './types'; +import ResourcePicker from '.'; + const noResourceURI = ''; const singleSubscriptionSelectionURI = '/subscriptions/def-456'; const singleResourceGroupSelectionURI = '/subscriptions/def-456/resourceGroups/dev-3'; @@ -100,7 +102,7 @@ describe('AzureMonitor ResourcePicker', () => { advancedSection.click(); const advancedInput = await screen.findByLabelText('Resource URI'); - userEvent.type(advancedInput, '/subscriptions/def-123'); + await userEvent.type(advancedInput, '/subscriptions/def-123'); const applyButton = screen.getByRole('button', { name: 'Apply' }); applyButton.click(); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx index 03c335b6690..16edcf26bf0 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx @@ -1,11 +1,13 @@ import { css } from '@emotion/css'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Button, Icon, Input, LoadingPlaceholder, Tooltip, useStyles2, Collapse, Label } from '@grafana/ui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; import ResourcePickerData from '../../resourcePicker/resourcePickerData'; import messageFromError from '../../utils/messageFromError'; import { Space } from '../Space'; + import NestedResourceTable from './NestedResourceTable'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; import { addResources, findRow, parseResourceURI } from './utils'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts index 957fec90642..9a1d65d2a24 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; + import { GrafanaTheme2 } from '@grafana/data'; const getStyles = (theme: GrafanaTheme2) => ({ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts index 7595a0ff2ba..9f29cd59b1c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/utils.ts @@ -1,4 +1,5 @@ import produce from 'immer'; + import { ResourceRow, ResourceRowGroup } from './types'; // This regex matches URIs representing: diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Space.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Space.tsx index 7b215ec1e64..b1fd5e33922 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Space.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/Space.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { css, cx } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { stylesFactory, useTheme2 } from '@grafana/ui'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx index 1c7fefef304..3e0b2e75b49 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/SubscriptionField.tsx @@ -1,9 +1,11 @@ import React, { useCallback, useMemo } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Select, MultiSelect } from '@grafana/ui'; import { AzureMonitorQuery, AzureQueryEditorFieldProps, AzureMonitorOption, AzureQueryType } from '../types'; import { findOptions } from '../utils/common'; + import { Field } from './Field'; interface SubscriptionFieldProps extends AzureQueryEditorFieldProps { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx index f9a233d1853..f3e7ada507d 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; -import VariableEditor from './VariableEditor'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { select } from 'react-select-event'; + +import * as ui from '@grafana/ui'; + import createMockDatasource from '../../__mocks__/datasource'; import { AzureMonitorQuery, AzureQueryType } from '../../types'; -import { select } from 'react-select-event'; -import * as ui from '@grafana/ui'; + +import VariableEditor from './VariableEditor'; // Have to mock CodeEditor because it doesnt seem to work in tests??? jest.mock('@grafana/ui', () => ({ @@ -137,7 +140,7 @@ describe('VariableEditor:', () => { }; render(); await waitFor(() => screen.queryByText('Grafana template variable function')); - userEvent.type(screen.getByDisplayValue('Su'), 'bscriptions()'); + await userEvent.type(screen.getByDisplayValue('Su'), 'bscriptions()'); expect(screen.getByDisplayValue('Subscriptions()')).toBeInTheDocument(); screen.getByDisplayValue('Subscriptions()').blur(); await waitFor(() => screen.queryByText('None')); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx index c29bb1fb66d..7dca2a9fdae 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx @@ -1,12 +1,14 @@ +import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { Alert, InlineField, Input, Select } from '@grafana/ui'; -import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; -import { AzureMonitorQuery, AzureQueryType } from '../../types'; -import LogsQueryEditor from '../LogsQueryEditor'; + import DataSource from '../../datasource'; -import useLastError from '../../utils/useLastError'; -import { Space } from '../Space'; import { migrateStringQueriesToObjectQueries } from '../../grafanaTemplateVariableFns'; +import { AzureMonitorQuery, AzureQueryType } from '../../types'; +import useLastError from '../../utils/useLastError'; +import LogsQueryEditor from '../LogsQueryEditor'; +import { Space } from '../Space'; const AZURE_QUERY_VARIABLE_TYPE_OPTIONS = [ { label: 'Grafana Query Function', value: AzureQueryType.GrafanaTemplateVariableFn }, diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.test.ts index 84a43170278..bd50d3b34c1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.test.ts @@ -1,9 +1,10 @@ import { lastValueFrom, of } from 'rxjs'; + import { DataFrame, getFrameDisplayName, toUtc } from '@grafana/data'; import { setBackendSrv } from '@grafana/runtime'; - -import { TemplateSrv } from 'app/features/templating/template_srv'; import { backendSrv } from 'app/core/services/backend_srv'; +import { TemplateSrv } from 'app/features/templating/template_srv'; + import AppInsightsDatasource from './app_insights_datasource'; const templateSrv = new TemplateSrv(); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.ts index 15931a06bbf..c94a9dad515 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/app_insights_datasource.ts @@ -1,6 +1,7 @@ +import { isString } from 'lodash'; + import { DataQueryRequest, DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; -import { isString } from 'lodash'; import TimegrainConverter from '../../../time_grain_converter'; import { @@ -10,6 +11,7 @@ import { DeprecatedAzureQueryType, } from '../../../types'; import { routeNames } from '../../../utils/common'; + import ResponseParser from './response_parser'; export interface LogAnalyticsColumn { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/response_parser.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/response_parser.ts index fbf8349c05a..bdba5d2640c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/response_parser.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/app_insights/response_parser.ts @@ -1,4 +1,5 @@ import { concat, filter, find, forEach, indexOf, intersection, isObject, map, without, keys as _keys } from 'lodash'; + import { dateTime } from '@grafana/data'; export default class ResponseParser { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.test.tsx index b6e1edea44d..9528c74900a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.test.tsx @@ -91,7 +91,7 @@ describe('Render', () => { updateOptions: onUpdate, })); expect(screen.queryByText('is no longer supported', { exact: false })).toBeInTheDocument(); - userEvent.click(screen.getByText('Clear Azure Monitor Logs Credentials')); + await userEvent.click(screen.getByText('Clear Azure Monitor Logs Credentials')); expect(onUpdate).toHaveBeenCalled(); const newOpts = onUpdate.mock.calls[0][0]({}); expect(newOpts).toEqual({ jsonData: { azureLogAnalyticsSameAs: true } }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.tsx index 17df310498e..b3276e4ab78 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/AnalyticsConfig.tsx @@ -1,6 +1,7 @@ -import { Alert, Button } from '@grafana/ui'; import React, { FunctionComponent, useMemo } from 'react'; +import { Alert, Button } from '@grafana/ui'; + import { getCredentials } from '../../../credentials'; import { AzureDataSourceSettings } from '../../../types'; import { AzureCredentialsForm } from '../../AzureCredentialsForm'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/ApplicationInsightsEditor/index.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/ApplicationInsightsEditor/index.tsx index 8f9d46ec30a..51adf3971f4 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/ApplicationInsightsEditor/index.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/ApplicationInsightsEditor/index.tsx @@ -1,6 +1,7 @@ -import { Alert, Input } from '@grafana/ui'; import React from 'react'; +import { Alert, Input } from '@grafana/ui'; + import { Field } from '../../../Field'; import { DeprecatedAzureMonitorQuery } from '../../types'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsAnalyticsEditor/index.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsAnalyticsEditor/index.tsx index 3c489130394..e1a69edfc5e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsAnalyticsEditor/index.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsAnalyticsEditor/index.tsx @@ -1,6 +1,7 @@ -import { Alert, CodeEditor, Select } from '@grafana/ui'; import React from 'react'; +import { Alert, CodeEditor, Select } from '@grafana/ui'; + import { AzureMonitorOption } from '../../../../types'; import { Field } from '../../../Field'; import { Space } from '../../../Space'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.test.tsx index 3c3e579701d..8137ed0319f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.test.tsx @@ -1,8 +1,10 @@ -import { Button, LegacyForms } from '@grafana/ui'; import { shallow } from 'enzyme'; import React from 'react'; +import { Button, LegacyForms } from '@grafana/ui'; + import { Props } from '../../ConfigEditor'; + import InsightsConfig from './InsightsConfig'; const { Input } = LegacyForms; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.tsx index 52669126a5e..a4ecf31bdee 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/components/InsightsConfig.tsx @@ -1,10 +1,11 @@ +import React, { PureComponent } from 'react'; + import { updateDatasourcePluginJsonDataOption, updateDatasourcePluginResetOption, updateDatasourcePluginSecureJsonDataOption, } from '@grafana/data'; import { Alert, Button, InlineFormLabel, LegacyForms } from '@grafana/ui'; -import React, { PureComponent } from 'react'; import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData } from '../../../types'; import { Props } from '../../ConfigEditor'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/utils.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/utils.tsx index ddf958cb4db..6c0e96bd4cd 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/utils.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/deprecated/utils.tsx @@ -1,6 +1,7 @@ -import { config } from '@grafana/runtime'; import { gt, valid } from 'semver'; +import { config } from '@grafana/runtime'; + import { AzureDataSourceSettings } from '../../types'; export function isAppInsightsConfigured(options: AzureDataSourceSettings) { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts index 42997fb09f8..81ea72a8997 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts @@ -1,3 +1,7 @@ +import { cloneDeep, upperFirst } from 'lodash'; +import { forkJoin, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; + import { DataFrame, DataQueryRequest, @@ -8,9 +12,6 @@ import { ScopedVars, } from '@grafana/data'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -import { cloneDeep, upperFirst } from 'lodash'; -import { forkJoin, Observable, of } from 'rxjs'; -import { map } from 'rxjs/operators'; import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource'; import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource'; @@ -218,49 +219,6 @@ export default class Datasource extends DataSourceApi { const expectedMigrations: Array<{ input: string; output: AzureMonitorQuery }> = [ diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts index d9ef473760e..9c6f2e3a707 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/log_analytics/querystring_builder.test.ts @@ -1,6 +1,7 @@ -import LogAnalyticsQuerystringBuilder from './querystring_builder'; import { dateTime } from '@grafana/data'; +import LogAnalyticsQuerystringBuilder from './querystring_builder'; + describe('LogAnalyticsDatasource', () => { let builder: LogAnalyticsQuerystringBuilder; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/module.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/module.ts index 1215c103b32..04653d79235 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/module.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/module.ts @@ -1,7 +1,8 @@ import { DataSourcePlugin } from '@grafana/data'; -import Datasource from './datasource'; + import { ConfigEditor } from './components/ConfigEditor'; import AzureMonitorQueryEditor from './components/QueryEditor'; +import Datasource from './datasource'; import { AzureMonitorQuery, AzureDataSourceJsonData } from './types'; export const plugin = new DataSourcePlugin(Datasource) diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html b/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html deleted file mode 100644 index 206e2789159..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/annotations.editor.html +++ /dev/null @@ -1,91 +0,0 @@ -
-
- -
- -
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
-
- -
-
- -
-
- -
- -
-
- -
-
- -
-
- -
-
- -
-
Annotation Query Format
-An annotation is an event that is overlaid on top of graphs. The query can have up to three columns per row, the datetime column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned. - -- column with the datetime type. -- column with alias: Text or text for the annotation text -- column with alias: Tags or tags for annotation tags. This is should return a comma separated string of tags e.g. 'tag1,tag2' - -Macros: - - $__timeFilter() -> TimeGenerated ≥ datetime(2018-06-05T18:09:58.907Z) and TimeGenerated ≤ datetime(2018-06-05T20:09:58.907Z) - - $__timeFilter(datetimeColumn) -> datetimeColumn ≥ datetime(2018-06-05T18:09:58.907Z) and datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z) - - Or build your own conditionals using these built-in variables which just return the values: - - $__timeFrom -> datetime(2018-06-05T18:09:58.907Z) - - $__timeTo -> datetime(2018-06-05T20:09:58.907Z) - - $__interval -> 5m -
-
-
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.test.ts index 4942b5089ca..5268dfca451 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/resourcePicker/resourcePickerData.test.ts @@ -4,9 +4,10 @@ import { createMockARGSubscriptionResponse, } from '../__mocks__/argResourcePickerResponse'; import { createMockInstanceSetttings } from '../__mocks__/instanceSettings'; -import ResourcePickerData from './resourcePickerData'; import { AzureGraphResponse } from '../types'; +import ResourcePickerData from './resourcePickerData'; + const createResourcePickerData = (responses: AzureGraphResponse[]) => { const instanceSettings = createMockInstanceSetttings(); const resourcePickerData = new ResourcePickerData(instanceSettings); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts index 2688f012aaa..66f8a689ebf 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/time_grain_converter.ts @@ -1,4 +1,5 @@ import { includes, filter } from 'lodash'; + import { rangeUtil } from '@grafana/data'; export default class TimeGrainConverter { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts index 86c9a2df88c..c312af6eae1 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts @@ -1,4 +1,5 @@ import { DeprecatedAzureMonitorQuery } from '../components/deprecated/types'; + import { GrafanaTemplateVariableQuery } from './templateVariables'; export enum AzureQueryType { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/types.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/types.ts index 9a6666fcf77..3c4bbe31ac5 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/types.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/types/types.ts @@ -1,6 +1,7 @@ import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceSettings, TableData } from '@grafana/data'; import Datasource from '../datasource'; + import { AzureMonitorQuery } from './query'; export type AzureDataSourceSettings = DataSourceSettings; @@ -209,3 +210,50 @@ export interface AzureResourceGraphOptions { allowPartialScopes: boolean; resultFormat: 'objectArray' | 'table'; } + +// Azure Monitor Metrics query API data fetcher argument types. +// The types prefixed by Legacy are applicable to pre-version 9 of Grafana +// that do not have a resourceUri, instead the resourceUri is built up from +// the subscription, resource group, metric definition (a.k.a. resource type) +// and the resource name. +export type GetMetricNamespacesQuery = AzureGetMetricNamespacesQuery | LegacyAzureGetMetricNamespacesQuery; +export type GetMetricNamesQuery = AzureGetMetricNamesQuery | LegacyAzureGetMetricNamesQuery; +export type GetMetricMetadataQuery = AzureGetMetricMetadataQuery | LegacyAzureGetMetricMetadataQuery; + +export interface AzureGetMetricNamespacesQuery { + resourceUri: string; +} +export interface LegacyAzureGetMetricNamespacesQuery { + subscription: string; + resourceGroup: string; + metricDefinition: string; + resourceName: string; +} + +export interface AzureGetMetricNamesQuery { + resourceUri: string; + metricNamespace: string; +} + +export interface LegacyAzureGetMetricNamesQuery { + subscription: string; + resourceGroup: string; + metricDefinition: string; + resourceName: string; + metricNamespace: string; +} + +export interface AzureGetMetricMetadataQuery { + resourceUri: string; + metricNamespace: string; + metricName: string; +} + +export interface LegacyAzureGetMetricMetadataQuery { + subscription: string; + resourceGroup: string; + metricDefinition: string; + resourceName: string; + metricNamespace: string; + metricName: string; +} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.test.ts index e16c273a14b..71630897de5 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.test.ts @@ -1,4 +1,5 @@ import { initialCustomVariableModelState } from 'app/features/variables/custom/reducer'; + import { hasOption, interpolateVariable } from './common'; describe('AzureMonitor: hasOption', () => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.ts index 9bcfae999d6..a07b6e7df10 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/common.ts @@ -1,5 +1,7 @@ import { map } from 'lodash'; + import { rangeUtil } from '@grafana/data'; + import TimegrainConverter from '../time_grain_converter'; import { AzureMonitorOption } from '../types'; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/messageFromError.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/messageFromError.test.ts index 9b178d93035..8af0b8c8e45 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/messageFromError.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/messageFromError.test.ts @@ -1,4 +1,5 @@ import { invalidNamespaceError } from '../__mocks__/errors'; + import messageFromError from './messageFromError'; describe('AzureMonitor: messageFromError', () => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.test.ts index 6d3380a76ab..c55c9a4e985 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.test.ts @@ -1,5 +1,7 @@ import { AnnotationQuery } from '@grafana/data'; + import { AzureMonitorQuery, AzureQueryType } from '../types'; + import migrateAnnotation from './migrateAnnotation'; const OLD_ANNOTATION: AnnotationQuery = { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.ts index 3dc8098e1fc..c630d34805f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateAnnotation.ts @@ -1,6 +1,7 @@ -import { AzureMonitorQuery, AzureQueryType } from '../types'; import { AnnotationQuery } from '@grafana/data'; +import { AzureMonitorQuery, AzureQueryType } from '../types'; + // The old Angular annotations editor put some properties (rawQuery, workspace, subscription) // on the root annotation object, rather than down in the 'targets' query value // This migration moves them to a Log Analytics query compatible with the React query editor diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts index 943de75fe7b..aaec47fc341 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.test.ts @@ -1,13 +1,63 @@ import { AzureMonitorQuery, AzureQueryType } from '../types'; + import migrateQuery from './migrateQuery'; +const azureMonitorQueryV7 = { + appInsights: { dimension: [], metricName: 'select', timeGrain: 'auto' }, + azureLogAnalytics: { + query: + '//change this example to create your own time series query\n //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full chart’s time range, choose the datetime column here\n| summarize count() by , bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc', + resultFormat: 'time_series', + workspace: 'mock-workspace-id', + }, + azureMonitor: { + aggregation: 'Average', + allowedTimeGrainsMs: [60000, 300000, 900000, 1800000, 3600000, 21600000, 43200000, 86400000], + dimensionFilters: [{ dimension: 'dependency/success', filter: '', operator: 'eq' }], + metricDefinition: 'microsoft.insights/components', + metricName: 'dependencies/duration', + metricNamespace: 'microsoft.insights/components', + resourceGroup: 'cloud-datasources', + resourceName: 'AppInsightsTestData', + timeGrain: 'auto', + top: '10', + }, + insightsAnalytics: { + query: '', + resultFormat: 'time_series', + }, + queryType: AzureQueryType.AzureMonitor, + refId: 'A', + subscription: '44693801-6ee6-49de-9b2d-9106972f9572', +}; + +const azureMonitorQueryV8 = { + azureMonitor: { + aggregation: 'Average', + dimensionFilters: [], + metricDefinition: 'microsoft.insights/components', + metricName: 'dependencies/duration', + metricNamespace: 'microsoft.insights/components', + resourceGroup: 'cloud-datasources', + resourceName: 'AppInsightsTestData', + timeGrain: 'auto', + }, + datasource: { + type: 'grafana-azure-monitor-datasource', + uid: 'sD-ZuB87k', + }, + queryType: AzureQueryType.AzureMonitor, + refId: 'A', + subscription: '44693801-6ee6-49de-9b2d-9106972f9572', +}; + const modernMetricsQuery: AzureMonitorQuery = { appInsights: { dimension: [], metricName: 'select', timeGrain: 'auto' }, azureLogAnalytics: { query: '//change this example to create your own time series query\n
//the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full chart’s time range, choose the datetime column here\n| summarize count() by , bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc', resultFormat: 'time_series', - workspace: 'e3fe4fde-ad5e-4d60-9974-e2f3562ffdf2', + workspace: 'mock-workspace-id', }, azureMonitor: { aggregation: 'Average', @@ -19,6 +69,8 @@ const modernMetricsQuery: AzureMonitorQuery = { metricNamespace: 'microsoft.insights/components', resourceGroup: 'cloud-datasources', resourceName: 'AppInsightsTestData', + resourceUri: + '/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData', timeGrain: 'PT5M', top: '10', }, @@ -37,4 +89,32 @@ describe('AzureMonitor: migrateQuery', () => { // MUST use .toBe because we want to assert that the identity of unmigrated queries remains the same expect(modernMetricsQuery).toBe(result); }); + + describe('migrating from a v7 query to the latest query version', () => { + it('should build a resource uri', () => { + const result = migrateQuery(azureMonitorQueryV7); + expect(result).toMatchObject( + expect.objectContaining({ + azureMonitor: expect.objectContaining({ + resourceUri: + '/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData', + }), + }) + ); + }); + }); + + describe('migrating from a v8 query to the latest query version', () => { + it('should build a resource uri', () => { + const result = migrateQuery(azureMonitorQueryV8); + expect(result).toMatchObject( + expect.objectContaining({ + azureMonitor: expect.objectContaining({ + resourceUri: + '/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData', + }), + }) + ); + }); + }); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts index 3a9043718e0..1c55b1c5b5e 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/migrateQuery.ts @@ -1,3 +1,4 @@ +import UrlBuilder from '../azure_monitor/url_builder'; import { setKustoQuery } from '../components/LogsQueryEditor/setQueryValue'; import { appendDimensionFilter, @@ -21,6 +22,7 @@ export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuer workingQuery = migrateToDefaultNamespace(workingQuery); workingQuery = migrateApplicationInsightsDimensions(workingQuery); workingQuery = migrateMetricsDimensionFilters(workingQuery); + workingQuery = migrateResourceUri(workingQuery); return workingQuery; } @@ -121,7 +123,6 @@ function migrateApplicationInsightsDimensions(query: AzureMonitorQuery): AzureMo return query; } -// Exported because its also used directly in the datasource.ts for some reason function migrateMetricsDimensionFilters(query: AzureMonitorQuery): AzureMonitorQuery { let workingQuery = query; @@ -133,6 +134,33 @@ function migrateMetricsDimensionFilters(query: AzureMonitorQuery): AzureMonitorQ return workingQuery; } +// Azure Monitor metric queries prior to Grafana version 9 did not include a `resourceUri`. +// The resourceUri was previously constructed with the subscription id, resource group, +// metric definition (a.k.a. resource type), and the resource name. +function migrateResourceUri(query: AzureMonitorQuery): AzureMonitorQuery { + const azureMonitorQuery = query.azureMonitor; + + if (!azureMonitorQuery || azureMonitorQuery.resourceUri) { + return query; + } + + const { subscription } = query; + const { resourceGroup, metricDefinition, resourceName } = azureMonitorQuery; + if (!(subscription && resourceGroup && metricDefinition && resourceName)) { + return query; + } + + const resourceUri = UrlBuilder.buildResourceUri(subscription, resourceGroup, metricDefinition, resourceName); + + return { + ...query, + azureMonitor: { + ...azureMonitorQuery, + resourceUri, + }, + }; +} + // datasource.ts also contains some migrations, which have been moved to here. Unsure whether // they should also do all the other migrations... export function datasourceMigrations(query: AzureMonitorQuery): AzureMonitorQuery { @@ -159,6 +187,7 @@ export function datasourceMigrations(query: AzureMonitorQuery): AzureMonitorQuer if (workingQuery.queryType === AzureQueryType.AzureMonitor && workingQuery.azureMonitor) { workingQuery = migrateMetricsDimensionFilters(workingQuery); + workingQuery = migrateResourceUri(workingQuery); } return workingQuery; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useAsyncState.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useAsyncState.test.ts index ddbfb2b5cc0..9d8cdb9d922 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useAsyncState.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useAsyncState.test.ts @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; + import { useAsyncState } from './useAsyncState'; interface WaitableMock extends jest.Mock { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.test.ts index 7fbccbe3a50..a725cc4cec5 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.test.ts @@ -1,4 +1,5 @@ import { renderHook, act } from '@testing-library/react-hooks'; + import useLastError from './useLastError'; describe('AzureMonitor: useLastError', () => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.ts index 851c3106b8c..8fb0246e1d5 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/utils/useLastError.ts @@ -1,5 +1,7 @@ import { useState, useCallback, useMemo } from 'react'; + import { AzureMonitorErrorish } from '../types'; + import messageFromError from './messageFromError'; type SourcedError = [string, AzureMonitorErrorish]; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.test.ts index e7f5ef9779e..21518be17c9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.test.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.test.ts @@ -1,6 +1,7 @@ -import { DataQueryRequest, DataQueryResponseData, toDataFrame } from '@grafana/data'; import { from } from 'rxjs'; +import { DataQueryRequest, DataQueryResponseData, toDataFrame } from '@grafana/data'; + import createMockDatasource from './__mocks__/datasource'; import { invalidSubscriptionError } from './__mocks__/errors'; import { AzureMonitorQuery, AzureQueryType } from './types'; @@ -270,7 +271,9 @@ describe('VariableSupport', () => { azureLogAnalyticsDatasource: { defaultSubscriptionId: 'defaultSubscriptionId', }, - getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults), + azureMonitorDatasource: { + getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults), + }, }) ); const mockRequest = { @@ -296,7 +299,9 @@ describe('VariableSupport', () => { const expectedResults = ['test']; const variableSupport = new VariableSupport( createMockDatasource({ - getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults), + azureMonitorDatasource: { + getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults), + }, }) ); const mockRequest = { @@ -325,7 +330,9 @@ describe('VariableSupport', () => { azureLogAnalyticsDatasource: { defaultSubscriptionId: 'defaultSubscriptionId', }, - getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults), + azureMonitorDatasource: { + getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults), + }, }) ); const mockRequest = { @@ -351,7 +358,9 @@ describe('VariableSupport', () => { const expectedResults = ['test']; const variableSupport = new VariableSupport( createMockDatasource({ - getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults), + azureMonitorDatasource: { + getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults), + }, }) ); const mockRequest = { @@ -478,7 +487,9 @@ describe('VariableSupport', () => { azureLogAnalyticsDatasource: { defaultSubscriptionId: 'defaultSubscriptionId', }, - getMetricNames: jest.fn().mockResolvedValueOnce([]), + azureMonitorDatasource: { + getMetricNames: jest.fn().mockResolvedValueOnce([]), + }, }) ); const mockRequest = { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts index 6c70fa479ab..c544ba116c7 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts @@ -1,3 +1,5 @@ +import { from, lastValueFrom, Observable } from 'rxjs'; + import { CustomVariableSupport, DataQueryRequest, @@ -6,7 +8,6 @@ import { toDataFrame, } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; -import { from, lastValueFrom, Observable } from 'rxjs'; import VariableEditor from './components/VariableEditor/VariableEditor'; import DataSource from './datasource'; @@ -81,22 +82,11 @@ export class VariableSupport extends CustomVariableSupport { diff --git a/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx index 0e956cd7e1b..10eec6cf254 100644 --- a/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx @@ -1,12 +1,13 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { SelectableValue } from '@grafana/data'; import { Field, FieldSet, Select, Switch } from '@grafana/ui'; -import { css } from '@emotion/css'; - import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; -import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery } from '../types'; import { getAnnotationTags } from 'app/features/annotations/api'; +import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery } from '../types'; + const matchTooltipContent = 'Enabling this returns annotations that match any of the tags specified below'; const tagsTooltipContent = ( diff --git a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx index 992fcf4f0e9..7fcaeea6464 100644 --- a/public/app/plugins/datasource/grafana/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana/components/QueryEditor.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { InlineField, Select, Alert, Input, InlineFieldRow } from '@grafana/ui'; + import { QueryEditorProps, SelectableValue, @@ -8,9 +8,11 @@ import { DataQueryRequest, DataFrame, } from '@grafana/data'; +import { config, getBackendSrv, getDataSourceSrv } from '@grafana/runtime'; +import { InlineField, Select, Alert, Input, InlineFieldRow } from '@grafana/ui'; + import { GrafanaDatasource } from '../datasource'; import { defaultQuery, GrafanaQuery, GrafanaQueryType } from '../types'; -import { config, getBackendSrv, getDataSourceSrv } from '@grafana/runtime'; type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/grafana/datasource.test.ts b/public/app/plugins/datasource/grafana/datasource.test.ts index daccdcd1bf5..a1a2183442d 100644 --- a/public/app/plugins/datasource/grafana/datasource.test.ts +++ b/public/app/plugins/datasource/grafana/datasource.test.ts @@ -1,6 +1,6 @@ import { AnnotationQueryRequest, DataSourceInstanceSettings, dateTime } from '@grafana/data'; - import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ + import { GrafanaDatasource } from './datasource'; import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery } from './types'; diff --git a/public/app/plugins/datasource/grafana/datasource.ts b/public/app/plugins/datasource/grafana/datasource.ts index 56c505af516..f9df9d68291 100644 --- a/public/app/plugins/datasource/grafana/datasource.ts +++ b/public/app/plugins/datasource/grafana/datasource.ts @@ -1,11 +1,7 @@ +import { isString } from 'lodash'; import { from, merge, Observable, of } from 'rxjs'; -import { - DataSourceWithBackend, - getBackendSrv, - getGrafanaLiveSrv, - getTemplateSrv, - StreamingFrameOptions, -} from '@grafana/runtime'; +import { map } from 'rxjs/operators'; + import { AnnotationQuery, AnnotationQueryRequest, @@ -19,13 +15,19 @@ import { parseLiveChannelAddress, toDataFrame, } from '@grafana/data'; - -import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQueryType } from './types'; -import AnnotationQueryEditor from './components/AnnotationQueryEditor'; -import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; -import { isString } from 'lodash'; +import { + DataSourceWithBackend, + getBackendSrv, + getGrafanaLiveSrv, + getTemplateSrv, + StreamingFrameOptions, +} from '@grafana/runtime'; import { migrateDatasourceNameToRef } from 'app/features/dashboard/state/DashboardMigrator'; -import { map } from 'rxjs/operators'; + +import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; + +import AnnotationQueryEditor from './components/AnnotationQueryEditor'; +import { GrafanaAnnotationQuery, GrafanaAnnotationType, GrafanaQuery, GrafanaQueryType } from './types'; let counter = 100; diff --git a/public/app/plugins/datasource/grafana/module.ts b/public/app/plugins/datasource/grafana/module.ts index 4fc4b8aaad4..930d5a4e680 100644 --- a/public/app/plugins/datasource/grafana/module.ts +++ b/public/app/plugins/datasource/grafana/module.ts @@ -1,6 +1,7 @@ import { DataSourcePlugin } from '@grafana/data'; -import { GrafanaDatasource } from './datasource'; + import { QueryEditor } from './components/QueryEditor'; +import { GrafanaDatasource } from './datasource'; import { GrafanaQuery } from './types'; export const plugin = new DataSourcePlugin(GrafanaDatasource).setQueryEditor( diff --git a/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx b/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx index 938494d6f43..1689697b554 100644 --- a/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx +++ b/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx @@ -1,12 +1,15 @@ +import { css, cx } from '@emotion/css'; import React, { useEffect, useMemo, useState } from 'react'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Button, Segment, useStyles2 } from '@grafana/ui'; + import { FuncDefs } from '../gfunc'; import { actions } from '../state/actions'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { css, cx } from '@emotion/css'; -import { mapFuncDefsToSelectables } from './helpers'; import { useDispatch } from '../state/context'; +import { mapFuncDefsToSelectables } from './helpers'; + type Props = { funcDefs: FuncDefs; }; diff --git a/public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx b/public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx index b8527aa1a4c..b9a6d609919 100644 --- a/public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { FunctionEditor } from './FunctionEditor'; +import React from 'react'; + import { FuncInstance } from '../gfunc'; +import { FunctionEditor } from './FunctionEditor'; + function mockFunctionInstance(name: string, unknown?: boolean): FuncInstance { const def = { category: 'category', diff --git a/public/app/plugins/datasource/graphite/components/FunctionEditor.tsx b/public/app/plugins/datasource/graphite/components/FunctionEditor.tsx index eecbf5b8673..1666614d0b7 100644 --- a/public/app/plugins/datasource/graphite/components/FunctionEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionEditor.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import { Icon, Tooltip, useStyles2 } from '@grafana/ui'; -import { FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls'; -import { FuncInstance } from '../gfunc'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, Tooltip, useStyles2 } from '@grafana/ui'; + +import { FuncInstance } from '../gfunc'; + +import { FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls'; interface FunctionEditorProps extends FunctionEditorControlsProps { func: FuncInstance; diff --git a/public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx b/public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx index f4d1fa52fa8..facd0b2511e 100644 --- a/public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx @@ -1,5 +1,7 @@ import React, { Suspense } from 'react'; + import { Icon, Tooltip } from '@grafana/ui'; + import { FuncInstance } from '../gfunc'; export interface FunctionEditorControlsProps { diff --git a/public/app/plugins/datasource/graphite/components/FunctionParamEditor.tsx b/public/app/plugins/datasource/graphite/components/FunctionParamEditor.tsx index 62847a932bb..d3306f4aab9 100644 --- a/public/app/plugins/datasource/graphite/components/FunctionParamEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionParamEditor.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { Segment, SegmentInput, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { Segment, SegmentInput, useStyles2 } from '@grafana/ui'; export type EditableParam = { name: string; diff --git a/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx b/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx index 81cce8c702a..0cbf5fb67a9 100644 --- a/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import { FuncDefs, FuncInstance } from '../gfunc'; -import { GraphiteFunctionEditor } from './GraphiteFunctionEditor'; -import { AddGraphiteFunction } from './AddGraphiteFunction'; + import { SegmentSection } from '@grafana/ui'; +import { FuncDefs, FuncInstance } from '../gfunc'; + +import { AddGraphiteFunction } from './AddGraphiteFunction'; +import { GraphiteFunctionEditor } from './GraphiteFunctionEditor'; + type Props = { functions: FuncInstance[]; funcDefs: FuncDefs; diff --git a/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx b/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx index fcf1b93cef3..aae6ce304c9 100644 --- a/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx @@ -1,14 +1,17 @@ -import React, { useState } from 'react'; -import { HorizontalGroup, InlineLabel, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; import { css, cx } from '@emotion/css'; +import React, { useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { HorizontalGroup, InlineLabel, useStyles2 } from '@grafana/ui'; + import { FuncInstance } from '../gfunc'; -import { EditableParam, FunctionParamEditor } from './FunctionParamEditor'; import { actions } from '../state/actions'; -import { FunctionEditor } from './FunctionEditor'; -import { mapFuncInstanceToParams } from './helpers'; import { useDispatch } from '../state/context'; +import { FunctionEditor } from './FunctionEditor'; +import { EditableParam, FunctionParamEditor } from './FunctionParamEditor'; +import { mapFuncInstanceToParams } from './helpers'; + export type FunctionEditorProps = { func: FuncInstance; }; diff --git a/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx b/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx index 567e086d891..2e0947bb503 100644 --- a/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx @@ -1,12 +1,15 @@ +import { css } from '@emotion/css'; import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { actions } from '../state/actions'; import { Button, useStyles2 } from '@grafana/ui'; + +import { actions } from '../state/actions'; import { GraphiteQueryEditorContext, GraphiteQueryEditorProps, useDispatch, useGraphiteState } from '../state/context'; + +import { FunctionsSection } from './FunctionsSection'; import { GraphiteTextEditor } from './GraphiteTextEditor'; import { SeriesSection } from './SeriesSection'; -import { FunctionsSection } from './FunctionsSection'; -import { css } from '@emotion/css'; export function GraphiteQueryEditor({ datasource, diff --git a/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx b/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx index 5941a4e3953..849b2477936 100644 --- a/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx @@ -1,5 +1,7 @@ import React, { useCallback } from 'react'; + import { QueryField } from '@grafana/ui'; + import { actions } from '../state/actions'; import { useDispatch } from '../state/context'; diff --git a/public/app/plugins/datasource/graphite/components/MetricSegment.tsx b/public/app/plugins/datasource/graphite/components/MetricSegment.tsx index ff4717fb7cb..af66dbfb5bf 100644 --- a/public/app/plugins/datasource/graphite/components/MetricSegment.tsx +++ b/public/app/plugins/datasource/graphite/components/MetricSegment.tsx @@ -1,12 +1,14 @@ -import React, { useCallback, useMemo } from 'react'; -import { SegmentAsync } from '@grafana/ui'; -import { actions } from '../state/actions'; -import { GraphiteSegment } from '../types'; -import { SelectableValue } from '@grafana/data'; -import { getAltSegmentsSelectables } from '../state/providers'; import { debounce } from 'lodash'; -import { GraphiteQueryEditorState } from '../state/store'; +import React, { useCallback, useMemo } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { SegmentAsync } from '@grafana/ui'; + +import { actions } from '../state/actions'; import { useDispatch } from '../state/context'; +import { getAltSegmentsSelectables } from '../state/providers'; +import { GraphiteQueryEditorState } from '../state/store'; +import { GraphiteSegment } from '../types'; type Props = { segment: GraphiteSegment; diff --git a/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx b/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx index 3a7ce13b615..f6fa47839b5 100644 --- a/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx +++ b/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx @@ -1,12 +1,14 @@ import { css, cx } from '@emotion/css'; import React, { PureComponent } from 'react'; + import { MetadataInspectorProps, rangeUtil } from '@grafana/data'; -import { GraphiteDatasource } from '../datasource'; -import { GraphiteQuery, GraphiteOptions, MetricTankSeriesMeta } from '../types'; -import { parseSchemaRetentions, getRollupNotice, getRuntimeConsolidationNotice } from '../meta'; import { stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; +import { GraphiteDatasource } from '../datasource'; +import { parseSchemaRetentions, getRollupNotice, getRuntimeConsolidationNotice } from '../meta'; +import { GraphiteQuery, GraphiteOptions, MetricTankSeriesMeta } from '../types'; + export type Props = MetadataInspectorProps; export interface State { diff --git a/public/app/plugins/datasource/graphite/components/MetricsSection.tsx b/public/app/plugins/datasource/graphite/components/MetricsSection.tsx index 34fb24160d4..28887f26a25 100644 --- a/public/app/plugins/datasource/graphite/components/MetricsSection.tsx +++ b/public/app/plugins/datasource/graphite/components/MetricsSection.tsx @@ -1,6 +1,8 @@ import React from 'react'; -import { GraphiteSegment } from '../types'; + import { GraphiteQueryEditorState } from '../state/store'; +import { GraphiteSegment } from '../types'; + import { MetricSegment } from './MetricSegment'; type Props = { diff --git a/public/app/plugins/datasource/graphite/components/PlayButton.tsx b/public/app/plugins/datasource/graphite/components/PlayButton.tsx index d17bf490632..18d76fefe7b 100644 --- a/public/app/plugins/datasource/graphite/components/PlayButton.tsx +++ b/public/app/plugins/datasource/graphite/components/PlayButton.tsx @@ -1,5 +1,7 @@ import React, { useCallback } from 'react'; + import { Button } from '@grafana/ui'; + import { actions } from '../state/actions'; import { useDispatch } from '../state/context'; diff --git a/public/app/plugins/datasource/graphite/components/SeriesSection.tsx b/public/app/plugins/datasource/graphite/components/SeriesSection.tsx index 17d5f47a598..5b76e2d897c 100644 --- a/public/app/plugins/datasource/graphite/components/SeriesSection.tsx +++ b/public/app/plugins/datasource/graphite/components/SeriesSection.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import { GraphiteQueryEditorState } from '../state/store'; -import { TagsSection } from './TagsSection'; -import { MetricsSection } from './MetricsSection'; + import { SegmentSection } from '@grafana/ui'; +import { GraphiteQueryEditorState } from '../state/store'; + +import { MetricsSection } from './MetricsSection'; +import { TagsSection } from './TagsSection'; + type Props = { state: GraphiteQueryEditorState; }; diff --git a/public/app/plugins/datasource/graphite/components/TagEditor.tsx b/public/app/plugins/datasource/graphite/components/TagEditor.tsx index 4a3f02f8aca..fe69a1bbad6 100644 --- a/public/app/plugins/datasource/graphite/components/TagEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/TagEditor.tsx @@ -1,11 +1,13 @@ +import { debounce } from 'lodash'; import React, { useCallback, useMemo } from 'react'; + import { Segment, SegmentAsync } from '@grafana/ui'; + import { actions } from '../state/actions'; -import { GraphiteTag, GraphiteTagOperator } from '../types'; +import { useDispatch } from '../state/context'; import { getTagOperatorsSelectables, getTagsSelectables, getTagValuesSelectables } from '../state/providers'; import { GraphiteQueryEditorState } from '../state/store'; -import { debounce } from 'lodash'; -import { useDispatch } from '../state/context'; +import { GraphiteTag, GraphiteTagOperator } from '../types'; type Props = { tag: GraphiteTag; diff --git a/public/app/plugins/datasource/graphite/components/TagsSection.tsx b/public/app/plugins/datasource/graphite/components/TagsSection.tsx index 72b5cb240cf..6c29e212257 100644 --- a/public/app/plugins/datasource/graphite/components/TagsSection.tsx +++ b/public/app/plugins/datasource/graphite/components/TagsSection.tsx @@ -1,16 +1,19 @@ -import React, { useCallback, useMemo } from 'react'; -import { GraphiteSegment } from '../types'; -import { GraphiteTag } from '../graphite_query'; -import { GraphiteQueryEditorState } from '../state/store'; -import { getTagsAsSegmentsSelectables } from '../state/providers'; -import { Button, SegmentAsync, useStyles2 } from '@grafana/ui'; -import { actions } from '../state/actions'; -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; -import { TagEditor } from './TagEditor'; import { debounce } from 'lodash'; +import React, { useCallback, useMemo } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, SegmentAsync, useStyles2 } from '@grafana/ui'; + +import { GraphiteTag } from '../graphite_query'; +import { actions } from '../state/actions'; import { useDispatch } from '../state/context'; +import { getTagsAsSegmentsSelectables } from '../state/providers'; +import { GraphiteQueryEditorState } from '../state/store'; +import { GraphiteSegment } from '../types'; + import { PlayButton } from './PlayButton'; +import { TagEditor } from './TagEditor'; type Props = { tags: GraphiteTag[]; diff --git a/public/app/plugins/datasource/graphite/components/helpers.test.ts b/public/app/plugins/datasource/graphite/components/helpers.test.ts index 25e179f23d4..52c8b1595f9 100644 --- a/public/app/plugins/datasource/graphite/components/helpers.test.ts +++ b/public/app/plugins/datasource/graphite/components/helpers.test.ts @@ -1,6 +1,7 @@ -import { mapFuncDefsToSelectables, mapFuncInstanceToParams } from './helpers'; import { FuncDef, FuncDefs, FuncInstance } from '../gfunc'; + import { EditableParam } from './FunctionParamEditor'; +import { mapFuncDefsToSelectables, mapFuncInstanceToParams } from './helpers'; function createFunctionInstance(funcDef: FuncDef, currentParams: string[]): FuncInstance { let funcInstance: FuncInstance = new FuncInstance(funcDef); diff --git a/public/app/plugins/datasource/graphite/components/helpers.ts b/public/app/plugins/datasource/graphite/components/helpers.ts index 39578b7edd7..1b40ad11950 100644 --- a/public/app/plugins/datasource/graphite/components/helpers.ts +++ b/public/app/plugins/datasource/graphite/components/helpers.ts @@ -1,9 +1,12 @@ -import { FuncDefs, FuncInstance, ParamDef } from '../gfunc'; import { forEach, sortBy } from 'lodash'; + import { SelectableValue } from '@grafana/data'; -import { EditableParam } from './FunctionParamEditor'; + +import { FuncDefs, FuncInstance, ParamDef } from '../gfunc'; import { GraphiteSegment } from '../types'; +import { EditableParam } from './FunctionParamEditor'; + export function mapStringsToSelectables(values: T[]): Array> { return values.map((value) => ({ value, diff --git a/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx index 88528f40237..ffe43a80037 100644 --- a/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx @@ -1,18 +1,21 @@ import React, { PureComponent } from 'react'; -import { Alert, DataSourceHttpSettings, InlineFormLabel, LegacyForms } from '@grafana/ui'; -const { Select, Switch } = LegacyForms; + import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption, onUpdateDatasourceJsonDataOptionSelect, onUpdateDatasourceJsonDataOptionChecked, } from '@grafana/data'; -import { GraphiteOptions, GraphiteType } from '../types'; -import { DEFAULT_GRAPHITE_VERSION, GRAPHITE_VERSIONS } from '../versions'; -import { MappingsConfiguration } from './MappingsConfiguration'; -import { fromString, toString } from './parseLokiLabelMappings'; +import { Alert, DataSourceHttpSettings, InlineFormLabel, LegacyForms } from '@grafana/ui'; import store from 'app/core/store'; +import { GraphiteOptions, GraphiteType } from '../types'; +import { DEFAULT_GRAPHITE_VERSION, GRAPHITE_VERSIONS } from '../versions'; + +import { MappingsConfiguration } from './MappingsConfiguration'; +import { fromString, toString } from './parseLokiLabelMappings'; + +const { Select, Switch } = LegacyForms; export const SHOW_MAPPINGS_HELP_KEY = 'grafana.datasources.graphite.config.showMappingsHelp'; const graphiteVersions = GRAPHITE_VERSIONS.map((version) => ({ label: `${version}.x`, value: version })); diff --git a/public/app/plugins/datasource/graphite/configuration/MappingsConfiguration.tsx b/public/app/plugins/datasource/graphite/configuration/MappingsConfiguration.tsx index c5cd2e5a401..bb593a1edb0 100644 --- a/public/app/plugins/datasource/graphite/configuration/MappingsConfiguration.tsx +++ b/public/app/plugins/datasource/graphite/configuration/MappingsConfiguration.tsx @@ -1,5 +1,7 @@ import React, { ChangeEvent, useState } from 'react'; + import { Button, Icon, InlineField, InlineFieldRow, Input } from '@grafana/ui'; + import MappingsHelp from './MappingsHelp'; type Props = { diff --git a/public/app/plugins/datasource/graphite/configuration/MappingsHelp.tsx b/public/app/plugins/datasource/graphite/configuration/MappingsHelp.tsx index a314ee7440d..a3b7b4f0dac 100644 --- a/public/app/plugins/datasource/graphite/configuration/MappingsHelp.tsx +++ b/public/app/plugins/datasource/graphite/configuration/MappingsHelp.tsx @@ -1,6 +1,7 @@ -import { Alert } from '@grafana/ui'; import React from 'react'; +import { Alert } from '@grafana/ui'; + type Props = { onDismiss: () => void; }; diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts index ed4a60ce3ee..23c5695a8ff 100644 --- a/public/app/plugins/datasource/graphite/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -1,13 +1,14 @@ -import { GraphiteDatasource } from './datasource'; import { isArray } from 'lodash'; - -import { TemplateSrv } from 'app/features/templating/template_srv'; -import { AbstractLabelMatcher, AbstractLabelOperator, dateTime, getFrameDisplayName } from '@grafana/data'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { of } from 'rxjs'; import { createFetchResponse } from 'test/helpers/createFetchResponse'; -import { DEFAULT_GRAPHITE_VERSION } from './versions'; + +import { AbstractLabelMatcher, AbstractLabelOperator, dateTime, getFrameDisplayName } from '@grafana/data'; +import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ +import { TemplateSrv } from 'app/features/templating/template_srv'; + import { fromString } from './configuration/parseLokiLabelMappings'; +import { GraphiteDatasource } from './datasource'; +import { DEFAULT_GRAPHITE_VERSION } from './versions'; jest.mock('@grafana/runtime', () => ({ ...(jest.requireActual('@grafana/runtime') as unknown as object), diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index f09ea680694..4a464822859 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -1,7 +1,7 @@ import { each, indexOf, isArray, isString, map as _map } from 'lodash'; import { lastValueFrom, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; -import { getBackendSrv } from '@grafana/runtime'; + import { DataFrame, DataQueryRequest, @@ -18,11 +18,15 @@ import { TimeRange, toDataFrame, } from '@grafana/data'; - +import { getBackendSrv } from '@grafana/runtime'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; -import gfunc, { FuncDefs, FuncInstance } from './gfunc'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -// Types +import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta'; + +import { getSearchFilterScopedVar } from '../../../features/variables/utils'; + +import gfunc, { FuncDefs, FuncInstance } from './gfunc'; +import { default as GraphiteQueryModel } from './graphite_query'; import { GraphiteLokiMapping, GraphiteMetricLokiMatcher, @@ -32,11 +36,8 @@ import { GraphiteType, MetricTankRequestMeta, } from './types'; -import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta'; -import { getSearchFilterScopedVar } from '../../../features/variables/utils'; -import { DEFAULT_GRAPHITE_VERSION } from './versions'; import { reduceError } from './utils'; -import { default as GraphiteQueryModel } from './graphite_query'; +import { DEFAULT_GRAPHITE_VERSION } from './versions'; const GRAPHITE_TAG_COMPARATORS = { '=': AbstractLabelOperator.Equal, diff --git a/public/app/plugins/datasource/graphite/gfunc.ts b/public/app/plugins/datasource/graphite/gfunc.ts index aee41af7dd3..44609e30824 100644 --- a/public/app/plugins/datasource/graphite/gfunc.ts +++ b/public/app/plugins/datasource/graphite/gfunc.ts @@ -1,6 +1,7 @@ import { assign, each, filter, forEach, get, includes, isString, last, map, toString, isFinite } from 'lodash'; -import { isVersionGtOrEq } from 'app/core/utils/version'; + import { InterpolateFunction } from '@grafana/data'; +import { isVersionGtOrEq } from 'app/core/utils/version'; export type ParamDef = { name: string; diff --git a/public/app/plugins/datasource/graphite/graphite_query.ts b/public/app/plugins/datasource/graphite/graphite_query.ts index da76d351e42..40232d19dfe 100644 --- a/public/app/plugins/datasource/graphite/graphite_query.ts +++ b/public/app/plugins/datasource/graphite/graphite_query.ts @@ -1,11 +1,13 @@ import { compact, each, findIndex, flatten, get, join, keyBy, last, map, reduce, without } from 'lodash'; -import { arrayMove } from 'app/core/utils/arrayMove'; -import { Parser } from './parser'; -import { TemplateSrv } from '@grafana/runtime'; + import { ScopedVars } from '@grafana/data'; -import { FuncInstance } from './gfunc'; -import { GraphiteSegment } from './types'; +import { TemplateSrv } from '@grafana/runtime'; +import { arrayMove } from 'app/core/utils/arrayMove'; + import { GraphiteDatasource } from './datasource'; +import { FuncInstance } from './gfunc'; +import { Parser } from './parser'; +import { GraphiteSegment } from './types'; export type GraphiteTagOperator = '=' | '=~' | '!=' | '!=~'; diff --git a/public/app/plugins/datasource/graphite/meta.ts b/public/app/plugins/datasource/graphite/meta.ts index 6bd24bd9406..1b1bcaf0631 100644 --- a/public/app/plugins/datasource/graphite/meta.ts +++ b/public/app/plugins/datasource/graphite/meta.ts @@ -1,6 +1,7 @@ -import { MetricTankSeriesMeta } from './types'; import { QueryResultMetaNotice } from '@grafana/data'; +import { MetricTankSeriesMeta } from './types'; + // https://github.com/grafana/metrictank/blob/master/scripts/config/storage-schemas.conf#L15-L46 export interface RetentionInfo { diff --git a/public/app/plugins/datasource/graphite/module.ts b/public/app/plugins/datasource/graphite/module.ts index 677be4fa678..39873ae4109 100644 --- a/public/app/plugins/datasource/graphite/module.ts +++ b/public/app/plugins/datasource/graphite/module.ts @@ -1,8 +1,9 @@ -import { GraphiteDatasource } from './datasource'; import { DataSourcePlugin } from '@grafana/data'; -import { ConfigEditor } from './configuration/ConfigEditor'; -import { MetricTankMetaInspector } from './components/MetricTankMetaInspector'; + import { GraphiteQueryEditor } from './components/GraphiteQueryEditor'; +import { MetricTankMetaInspector } from './components/MetricTankMetaInspector'; +import { ConfigEditor } from './configuration/ConfigEditor'; +import { GraphiteDatasource } from './datasource'; class AnnotationsQueryCtrl { static templateUrl = 'partials/annotations.editor.html'; diff --git a/public/app/plugins/datasource/graphite/specs/graphite_query.test.ts b/public/app/plugins/datasource/graphite/specs/graphite_query.test.ts index 16ab9616b01..0d0f516f474 100644 --- a/public/app/plugins/datasource/graphite/specs/graphite_query.test.ts +++ b/public/app/plugins/datasource/graphite/specs/graphite_query.test.ts @@ -1,6 +1,7 @@ +import { TemplateSrvStub } from 'test/specs/helpers'; + import gfunc from '../gfunc'; import GraphiteQuery from '../graphite_query'; -import { TemplateSrvStub } from 'test/specs/helpers'; describe('Graphite query model', () => { const ctx: any = { diff --git a/public/app/plugins/datasource/graphite/specs/store.test.ts b/public/app/plugins/datasource/graphite/specs/store.test.ts index b0e15713c1e..262e606e741 100644 --- a/public/app/plugins/datasource/graphite/specs/store.test.ts +++ b/public/app/plugins/datasource/graphite/specs/store.test.ts @@ -1,6 +1,8 @@ -import { dispatch } from 'app/store/store'; -import gfunc from '../gfunc'; import { TemplateSrvStub } from 'test/specs/helpers'; + +import { dispatch } from 'app/store/store'; + +import gfunc from '../gfunc'; import { actions } from '../state/actions'; import { getAltSegmentsSelectables, @@ -8,8 +10,8 @@ import { getTagsAsSegmentsSelectables, getTagValuesSelectables, } from '../state/providers'; -import { GraphiteSegment } from '../types'; import { createStore } from '../state/store'; +import { GraphiteSegment } from '../types'; jest.mock('app/angular/promiseToDigest', () => ({ promiseToDigest: (scope: any) => { diff --git a/public/app/plugins/datasource/graphite/state/actions.ts b/public/app/plugins/datasource/graphite/state/actions.ts index 555a1639b0e..2022da0de92 100644 --- a/public/app/plugins/datasource/graphite/state/actions.ts +++ b/public/app/plugins/datasource/graphite/state/actions.ts @@ -1,8 +1,10 @@ -import { GraphiteQuery, GraphiteQueryEditorDependencies, GraphiteSegment, GraphiteTag } from '../types'; import { createAction } from '@reduxjs/toolkit'; -import { FuncInstance } from '../gfunc'; + import { TimeRange } from '@grafana/data'; +import { FuncInstance } from '../gfunc'; +import { GraphiteQuery, GraphiteQueryEditorDependencies, GraphiteSegment, GraphiteTag } from '../types'; + /** * List of possible actions changing the state of QueryEditor */ diff --git a/public/app/plugins/datasource/graphite/state/context.tsx b/public/app/plugins/datasource/graphite/state/context.tsx index 2a4f303e62b..2565431e5bc 100644 --- a/public/app/plugins/datasource/graphite/state/context.tsx +++ b/public/app/plugins/datasource/graphite/state/context.tsx @@ -1,12 +1,15 @@ -import React, { createContext, Dispatch, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react'; import { AnyAction } from '@reduxjs/toolkit'; +import React, { createContext, Dispatch, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react'; +import { usePrevious } from 'react-use'; + import { QueryEditorProps } from '@grafana/data'; +import { getTemplateSrv } from 'app/features/templating/template_srv'; + import { GraphiteDatasource } from '../datasource'; import { GraphiteOptions, GraphiteQuery } from '../types'; -import { createStore, GraphiteQueryEditorState } from './store'; -import { getTemplateSrv } from 'app/features/templating/template_srv'; + import { actions } from './actions'; -import { usePrevious } from 'react-use'; +import { createStore, GraphiteQueryEditorState } from './store'; const DispatchContext = createContext>({} as Dispatch); const GraphiteStateContext = createContext({} as GraphiteQueryEditorState); diff --git a/public/app/plugins/datasource/graphite/state/helpers.ts b/public/app/plugins/datasource/graphite/state/helpers.ts index 6c235d77280..3378edf16a2 100644 --- a/public/app/plugins/datasource/graphite/state/helpers.ts +++ b/public/app/plugins/datasource/graphite/state/helpers.ts @@ -1,11 +1,13 @@ -import { GraphiteQueryEditorState } from './store'; import { clone } from 'lodash'; -import { dispatch } from '../../../../store/store'; -import { notifyApp } from '../../../../core/reducers/appNotification'; + import { createErrorNotification } from '../../../../core/copy/appNotification'; +import { notifyApp } from '../../../../core/reducers/appNotification'; +import { dispatch } from '../../../../store/store'; import { FuncInstance } from '../gfunc'; import { GraphiteQuery, GraphiteTagOperator } from '../types'; +import { GraphiteQueryEditorState } from './store'; + /** * Helpers used by reducers and providers. They modify state object directly so should operate on a copy of the state. */ diff --git a/public/app/plugins/datasource/graphite/state/providers.ts b/public/app/plugins/datasource/graphite/state/providers.ts index bc2d73e3e56..6db0021d96a 100644 --- a/public/app/plugins/datasource/graphite/state/providers.ts +++ b/public/app/plugins/datasource/graphite/state/providers.ts @@ -1,14 +1,17 @@ -import { GraphiteQueryEditorState } from './store'; import { eachRight, map, remove } from 'lodash'; + +import { SelectableValue } from '@grafana/data'; + +import { mapSegmentsToSelectables, mapStringsToSelectables } from '../components/helpers'; +import { GraphiteSegment, GraphiteTag, GraphiteTagOperator } from '../types'; + import { TAG_PREFIX, GRAPHITE_TAG_OPERATORS, handleMetricsAutoCompleteError, handleTagsAutoCompleteError, } from './helpers'; -import { GraphiteSegment, GraphiteTag, GraphiteTagOperator } from '../types'; -import { mapSegmentsToSelectables, mapStringsToSelectables } from '../components/helpers'; -import { SelectableValue } from '@grafana/data'; +import { GraphiteQueryEditorState } from './store'; /** * All auto-complete lists are updated while typing. To avoid performance issues we do not render more diff --git a/public/app/plugins/datasource/graphite/state/store.ts b/public/app/plugins/datasource/graphite/state/store.ts index 4aeb9ea3e9c..2bd3842df78 100644 --- a/public/app/plugins/datasource/graphite/state/store.ts +++ b/public/app/plugins/datasource/graphite/state/store.ts @@ -1,9 +1,16 @@ +import { AnyAction } from '@reduxjs/toolkit'; +import { Action, Dispatch } from 'redux'; + +import { DataQuery, TimeRange } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; + +import { TemplateSrv } from '../../../../features/templating/template_srv'; +import { GraphiteDatasource } from '../datasource'; +import { FuncDefs } from '../gfunc'; import GraphiteQuery, { GraphiteTarget } from '../graphite_query'; import { GraphiteSegment, GraphiteTagOperator } from '../types'; -import { GraphiteDatasource } from '../datasource'; -import { TemplateSrv } from '../../../../features/templating/template_srv'; + import { actions } from './actions'; -import { getTemplateSrv } from '@grafana/runtime'; import { addSeriesByTagFunc, buildSegments, @@ -16,10 +23,6 @@ import { smartlyHandleNewAliasByNode, spliceSegments, } from './helpers'; -import { Action, Dispatch } from 'redux'; -import { FuncDefs } from '../gfunc'; -import { AnyAction } from '@reduxjs/toolkit'; -import { DataQuery, TimeRange } from '@grafana/data'; export type GraphiteQueryEditorState = { // external dependencies diff --git a/public/app/plugins/datasource/graphite/types.ts b/public/app/plugins/datasource/graphite/types.ts index 64387b1b56e..ce5d5436d85 100644 --- a/public/app/plugins/datasource/graphite/types.ts +++ b/public/app/plugins/datasource/graphite/types.ts @@ -1,7 +1,9 @@ import { DataQuery, DataSourceJsonData, TimeRange } from '@grafana/data'; -import { GraphiteDatasource } from './datasource'; + import { TemplateSrv } from '../../../features/templating/template_srv'; +import { GraphiteDatasource } from './datasource'; + export interface GraphiteQuery extends DataQuery { target?: string; } diff --git a/public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx b/public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx index 8e8e6ed069d..b77656b1dfe 100644 --- a/public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/ConfigEditor.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import ConfigEditor, { Props } from './ConfigEditor'; jest.mock('lodash', () => { diff --git a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx index e742899e8ba..acffd37a53f 100644 --- a/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/ConfigEditor.tsx @@ -1,5 +1,6 @@ -import React, { PureComponent } from 'react'; import { uniqueId } from 'lodash'; +import React, { PureComponent } from 'react'; + import { DataSourcePluginOptionsEditorProps, SelectableValue, @@ -11,6 +12,7 @@ import { updateDatasourcePluginJsonDataOption, } from '@grafana/data'; import { Alert, DataSourceHttpSettings, InfoBox, InlineField, InlineFormLabel, LegacyForms, Select } from '@grafana/ui'; + const { Input, SecretFormField } = LegacyForms; import { InfluxOptions, InfluxSecureJsonData, InfluxVersion } from '../types'; diff --git a/public/app/plugins/datasource/influxdb/components/FluxQueryEditor.tsx b/public/app/plugins/datasource/influxdb/components/FluxQueryEditor.tsx index 19211ed4381..feaa90b2503 100644 --- a/public/app/plugins/datasource/influxdb/components/FluxQueryEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/FluxQueryEditor.tsx @@ -1,7 +1,8 @@ -import React, { PureComponent } from 'react'; -import { InfluxQuery } from '../types'; -import { SelectableValue } from '@grafana/data'; import { cx, css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; import { InlineFormLabel, LinkButton, @@ -11,8 +12,9 @@ import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind, } from '@grafana/ui'; -import { getTemplateSrv } from '@grafana/runtime'; + import InfluxDatasource from '../datasource'; +import { InfluxQuery } from '../types'; type Props = { onChange: (query: InfluxQuery) => void; diff --git a/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx b/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx index 846d0155ea7..f5ac86a24d2 100644 --- a/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx +++ b/public/app/plugins/datasource/influxdb/components/InfluxStartPage.tsx @@ -1,5 +1,7 @@ import React, { PureComponent } from 'react'; + import { QueryEditorHelpProps } from '@grafana/data'; + import InfluxCheatSheet from './InfluxCheatSheet'; export default class InfluxStartPage extends PureComponent { diff --git a/public/app/plugins/datasource/influxdb/components/QueryEditor.tsx b/public/app/plugins/datasource/influxdb/components/QueryEditor.tsx index 3501a793e37..9d736ba9b7d 100644 --- a/public/app/plugins/datasource/influxdb/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/QueryEditor.tsx @@ -1,13 +1,16 @@ -import React from 'react'; import { css } from '@emotion/css'; +import React from 'react'; + import { QueryEditorProps } from '@grafana/data'; -import { InfluxOptions, InfluxQuery } from '../types'; + import InfluxDatasource from '../datasource'; +import { buildRawQuery } from '../queryUtils'; +import { InfluxOptions, InfluxQuery } from '../types'; + import { FluxQueryEditor } from './FluxQueryEditor'; +import { QueryEditorModeSwitcher } from './QueryEditorModeSwitcher'; import { RawInfluxQLEditor } from './RawInfluxQLEditor'; import { Editor as VisualInfluxQLEditor } from './VisualInfluxQLEditor/Editor'; -import { QueryEditorModeSwitcher } from './QueryEditorModeSwitcher'; -import { buildRawQuery } from '../queryUtils'; type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/influxdb/components/QueryEditorModeSwitcher.tsx b/public/app/plugins/datasource/influxdb/components/QueryEditorModeSwitcher.tsx index 745f5849ac3..539ee63f4d5 100644 --- a/public/app/plugins/datasource/influxdb/components/QueryEditorModeSwitcher.tsx +++ b/public/app/plugins/datasource/influxdb/components/QueryEditorModeSwitcher.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; + import { Button, ConfirmModal } from '@grafana/ui'; type Props = { diff --git a/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx index 74a6ce5d65c..58b9a93e0f7 100644 --- a/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { selectOptionInTest } from '@grafana/ui'; -import { RawInfluxQLEditor } from './RawInfluxQLEditor'; + import { InfluxQuery } from '../types'; +import { RawInfluxQLEditor } from './RawInfluxQLEditor'; + const query: InfluxQuery = { refId: 'A', query: 'test query 1', @@ -74,7 +77,7 @@ describe('RawInfluxQLEditor', () => { // value before expect(queryTextarea).toHaveValue('test query 1'); - userEvent.type(queryTextarea, 'new changes'); + await userEvent.type(queryTextarea, 'new changes'); // the field should have a new value, but no onChange yet. expect(queryTextarea).toHaveValue('test query 1new changes'); @@ -97,7 +100,7 @@ describe('RawInfluxQLEditor', () => { // value before expect(aliasInput).toHaveValue('alias42'); - userEvent.type(aliasInput, 'new changes'); + await userEvent.type(aliasInput, 'new changes'); // the field should have a new value, but no onChange yet. expect(aliasInput).toHaveValue('alias42new changes'); diff --git a/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx index d0cf13302a8..b815d191556 100644 --- a/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx @@ -1,9 +1,12 @@ import React from 'react'; + import { TextArea, InlineFormLabel, Input, Select, HorizontalGroup } from '@grafana/ui'; + import { InfluxQuery } from '../types'; + +import { RESULT_FORMATS, DEFAULT_RESULT_FORMAT } from './constants'; import { useShadowedState } from './useShadowedState'; import { useUniqueId } from './useUniqueId'; -import { RESULT_FORMATS, DEFAULT_RESULT_FORMAT } from './constants'; type Props = { query: InfluxQuery; diff --git a/public/app/plugins/datasource/influxdb/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/influxdb/components/VariableQueryEditor.tsx index 0ca9b6958c9..bf5c9170945 100644 --- a/public/app/plugins/datasource/influxdb/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/influxdb/components/VariableQueryEditor.tsx @@ -1,6 +1,9 @@ import React, { PureComponent } from 'react'; -import InfluxDatasource from '../datasource'; + import { InlineFormLabel, TextArea } from '@grafana/ui'; + +import InfluxDatasource from '../datasource'; + import { FluxQueryEditor } from './FluxQueryEditor'; interface Props { diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/AddButton.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/AddButton.tsx index 2e7248eca68..8e9cde460d0 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/AddButton.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/AddButton.tsx @@ -1,5 +1,7 @@ import React from 'react'; + import { SelectableValue } from '@grafana/data'; + import { Seg } from './Seg'; import { unwrap } from './unwrap'; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx index 91d9e92cbb0..eccbb4d4d3a 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tags.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; -import { InfluxQuery } from '../../types'; -import InfluxDatasource from '../../datasource'; -import { render, screen, act } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { Editor } from './Editor'; +import React from 'react'; + +import InfluxDatasource from '../../datasource'; import * as mockedMeta from '../../influxQLMetadataQuery'; +import { InfluxQuery } from '../../types'; + +import { Editor } from './Editor'; jest.mock('../../influxQLMetadataQuery', () => { return { @@ -95,9 +97,7 @@ describe('InfluxDB InfluxQL Visual Editor field-filtering', () => { expect(mockedMeta.getTagKeysForMeasurementAndTags).toHaveBeenCalledTimes(1); // we click the WHERE/cpu button - await act(async () => { - userEvent.click(screen.getByRole('button', { name: 'cpu' })); - }); + await userEvent.click(screen.getByRole('button', { name: 'cpu' })); // and verify getTagKeysForMeasurementAndTags was called again, // and in the tags-param we did not receive the `field1` part. @@ -105,18 +105,14 @@ describe('InfluxDB InfluxQL Visual Editor field-filtering', () => { expect((mockedMeta.getTagKeysForMeasurementAndTags as jest.Mock).mock.calls[1][2]).toStrictEqual(ONLY_TAGS); // now we click on the WHERE/host2 button - await act(async () => { - userEvent.click(screen.getByRole('button', { name: 'host2' })); - }); + await userEvent.click(screen.getByRole('button', { name: 'host2' })); // verify `getTagValues` was called once, and in the tags-param we did not receive `field1` expect(mockedMeta.getTagValues).toHaveBeenCalledTimes(1); expect((mockedMeta.getTagValues as jest.Mock).mock.calls[0][3]).toStrictEqual(ONLY_TAGS); // now we click on the FROM/cpudata button - await act(async () => { - userEvent.click(screen.getByRole('button', { name: 'cpudata' })); - }); + await userEvent.click(screen.getByRole('button', { name: 'cpudata' })); // verify `getTagValues` was called once, and in the tags-param we did not receive `field1` expect(mockedMeta.getAllMeasurementsForTags).toHaveBeenCalledTimes(1); diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx index 32772721c3e..a45d8d0f462 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { InfluxQuery } from '../../types'; -import InfluxDatasource from '../../datasource'; import { render } from '@testing-library/react'; +import React from 'react'; + +import InfluxDatasource from '../../datasource'; +import { InfluxQuery } from '../../types'; + import { Editor } from './Editor'; // we mock the @grafana/ui components we use to make sure they just show their "value". diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx index c32be940678..4000c6d422f 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx @@ -1,12 +1,11 @@ +import { css } from '@emotion/css'; import React, { useMemo } from 'react'; -import { InfluxQuery, InfluxQueryTag } from '../../types'; + +import { GrafanaTheme2 } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; +import { InlineLabel, SegmentSection, useStyles2 } from '@grafana/ui'; + import InfluxDatasource from '../../datasource'; -import { FromSection } from './FromSection'; -import { TagsSection } from './TagsSection'; -import { PartListSection } from './PartListSection'; -import { OrderByTimeSection } from './OrderByTimeSection'; -import { InputSection } from './InputSection'; import { getAllMeasurementsForTags, getAllPolicies, @@ -23,14 +22,18 @@ import { changeSelectPart, changeGroupByPart, } from '../../queryUtils'; -import { FormatAsSection } from './FormatAsSection'; +import { InfluxQuery, InfluxQueryTag } from '../../types'; import { DEFAULT_RESULT_FORMAT } from '../constants'; -import { getNewSelectPartOptions, getNewGroupByPartOptions, makePartList } from './partListUtils'; -import { InlineLabel, SegmentSection, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2 } from '@grafana/data'; -import { css } from '@emotion/css'; import { useUniqueId } from '../useUniqueId'; +import { FormatAsSection } from './FormatAsSection'; +import { FromSection } from './FromSection'; +import { InputSection } from './InputSection'; +import { OrderByTimeSection } from './OrderByTimeSection'; +import { PartListSection } from './PartListSection'; +import { TagsSection } from './TagsSection'; +import { getNewSelectPartOptions, getNewGroupByPartOptions, makePartList } from './partListUtils'; + type Props = { query: InfluxQuery; onChange: (query: InfluxQuery) => void; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FormatAsSection.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FormatAsSection.tsx index 20598e649f0..f691181cb48 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FormatAsSection.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FormatAsSection.tsx @@ -1,10 +1,13 @@ -import { Select } from '@grafana/ui'; import { cx } from '@emotion/css'; -import { ResultFormat } from '../../types'; import React from 'react'; -import { unwrap } from './unwrap'; + +import { Select } from '@grafana/ui'; + +import { ResultFormat } from '../../types'; import { RESULT_FORMATS } from '../constants'; + import { paddingRightClass } from './styles'; +import { unwrap } from './unwrap'; type Props = { inputId?: string; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FromSection.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FromSection.tsx index 8ad227edf63..d3b4c7920ac 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FromSection.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/FromSection.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { Seg } from './Seg'; import { toSelectableValue } from './toSelectableValue'; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/InputSection.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/InputSection.tsx index 70edef5b984..3ecb355f552 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/InputSection.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/InputSection.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { cx } from '@emotion/css'; +import React from 'react'; + import { Input } from '@grafana/ui'; + import { useShadowedState } from '../useShadowedState'; + import { paddingRightClass } from './styles'; type Props = { diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/OrderByTimeSection.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/OrderByTimeSection.tsx index 90f38aae79e..f870e518e4f 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/OrderByTimeSection.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/OrderByTimeSection.tsx @@ -1,9 +1,11 @@ -import React from 'react'; import { cx } from '@emotion/css'; +import React from 'react'; + import { SelectableValue } from '@grafana/data'; -import { unwrap } from './unwrap'; import { Select } from '@grafana/ui'; + import { paddingRightClass } from './styles'; +import { unwrap } from './unwrap'; type Mode = 'ASC' | 'DESC'; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/PartListSection.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/PartListSection.tsx index 54c3bc1fbd1..23d3a94a75c 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/PartListSection.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/PartListSection.tsx @@ -1,11 +1,13 @@ -import React, { useMemo } from 'react'; import { cx, css } from '@emotion/css'; -import { MenuItem, WithContextMenu, MenuGroup, useTheme2 } from '@grafana/ui'; +import React, { useMemo } from 'react'; + import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; -import { Seg } from './Seg'; -import { unwrap } from './unwrap'; -import { toSelectableValue } from './toSelectableValue'; +import { MenuItem, WithContextMenu, MenuGroup, useTheme2 } from '@grafana/ui'; + import { AddButton } from './AddButton'; +import { Seg } from './Seg'; +import { toSelectableValue } from './toSelectableValue'; +import { unwrap } from './unwrap'; export type PartParams = Array<{ value: string; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Seg.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Seg.tsx index d7ee73c1ba4..a64203668e0 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Seg.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Seg.tsx @@ -1,9 +1,11 @@ -import React, { useState, useEffect } from 'react'; -import debouncePromise from 'debounce-promise'; import { cx, css } from '@emotion/css'; -import { SelectableValue } from '@grafana/data'; +import debouncePromise from 'debounce-promise'; +import React, { useState, useEffect } from 'react'; import { useAsyncFn } from 'react-use'; + +import { SelectableValue } from '@grafana/data'; import { InlineLabel, Select, AsyncSelect, Input } from '@grafana/ui'; + import { useShadowedState } from '../useShadowedState'; // this file is a simpler version of `grafana-ui / SegmentAsync.tsx` diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.test.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.test.tsx index c276373684a..e6589b62b16 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { fireEvent, render, screen, act, waitFor } from '@testing-library/react'; -import { TagsSection } from './TagsSection'; +import React from 'react'; + import { InfluxQueryTag } from '../../types'; +import { TagsSection } from './TagsSection'; + function getTagKeys() { return Promise.resolve(['t1', 't2', 't3', 't4', 't5', 't6']); } diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.tsx index eb4a067cb68..051c2f1ae9f 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/TagsSection.tsx @@ -1,10 +1,13 @@ import React from 'react'; + import { SelectableValue } from '@grafana/data'; -import { Seg } from './Seg'; + import { InfluxQueryTag } from '../../types'; -import { toSelectableValue } from './toSelectableValue'; -import { adjustOperatorIfNeeded, getCondition, getOperator } from './tagUtils'; + import { AddButton } from './AddButton'; +import { Seg } from './Seg'; +import { adjustOperatorIfNeeded, getCondition, getOperator } from './tagUtils'; +import { toSelectableValue } from './toSelectableValue'; type KnownOperator = '=' | '!=' | '<>' | '<' | '>' | '=~' | '!~'; const knownOperators: KnownOperator[] = ['=', '!=', '<>', '<', '>', '=~', '!~']; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/partListUtils.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/partListUtils.tsx index bd5eb0663bc..c3b76c1002b 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/partListUtils.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/partListUtils.tsx @@ -1,12 +1,14 @@ -import { InfluxQuery, InfluxQueryPart } from '../../types'; import { SelectableValue } from '@grafana/data'; -import { PartParams } from './PartListSection'; -import InfluxQueryModel from '../../influx_query_model'; -import { unwrap } from './unwrap'; -import queryPart from '../../query_part'; -import { toSelectableValue } from './toSelectableValue'; import { QueryPartDef } from 'app/features/alerting/state/query_part'; +import InfluxQueryModel from '../../influx_query_model'; +import queryPart from '../../query_part'; +import { InfluxQuery, InfluxQueryPart } from '../../types'; + +import { PartParams } from './PartListSection'; +import { toSelectableValue } from './toSelectableValue'; +import { unwrap } from './unwrap'; + type Categories = Record; export function getNewSelectPartOptions(): SelectableValue[] { diff --git a/public/app/plugins/datasource/influxdb/components/constants.ts b/public/app/plugins/datasource/influxdb/components/constants.ts index 9e80560a598..dcf1910b359 100644 --- a/public/app/plugins/datasource/influxdb/components/constants.ts +++ b/public/app/plugins/datasource/influxdb/components/constants.ts @@ -1,4 +1,5 @@ import { SelectableValue } from '@grafana/data'; + import { ResultFormat } from '../types'; export const RESULT_FORMATS: Array> = [ diff --git a/public/app/plugins/datasource/influxdb/components/useShadowedState.test.ts b/public/app/plugins/datasource/influxdb/components/useShadowedState.test.ts index 1e38b366e33..d339123ef40 100644 --- a/public/app/plugins/datasource/influxdb/components/useShadowedState.test.ts +++ b/public/app/plugins/datasource/influxdb/components/useShadowedState.test.ts @@ -1,4 +1,5 @@ import { renderHook, act } from '@testing-library/react-hooks'; + import { useShadowedState } from './useShadowedState'; describe('useShadowedState', () => { diff --git a/public/app/plugins/datasource/influxdb/components/useUniqueId.test.ts b/public/app/plugins/datasource/influxdb/components/useUniqueId.test.ts index ade77dd8c8c..57c5483cc50 100644 --- a/public/app/plugins/datasource/influxdb/components/useUniqueId.test.ts +++ b/public/app/plugins/datasource/influxdb/components/useUniqueId.test.ts @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; + import { useUniqueId } from './useUniqueId'; describe('useUniqueId', () => { diff --git a/public/app/plugins/datasource/influxdb/components/useUniqueId.ts b/public/app/plugins/datasource/influxdb/components/useUniqueId.ts index 8790060b794..6ff4ab7c4b0 100644 --- a/public/app/plugins/datasource/influxdb/components/useUniqueId.ts +++ b/public/app/plugins/datasource/influxdb/components/useUniqueId.ts @@ -1,5 +1,5 @@ -import { useRef } from 'react'; import { uniqueId } from 'lodash'; +import { useRef } from 'react'; export function useUniqueId(): string { // we need to lazy-init this ref. diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 2a26e4ccd5f..3001fcd2467 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -2,13 +2,7 @@ import { cloneDeep, extend, get, groupBy, has, isString, map as _map, omit, pick import { lastValueFrom, Observable, of, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { v4 as uuidv4 } from 'uuid'; -import { - BackendDataSourceResponse, - DataSourceWithBackend, - FetchResponse, - frameToMetricFindValue, - getBackendSrv, -} from '@grafana/runtime'; + import { AnnotationEvent, ArrayVector, @@ -29,15 +23,23 @@ import { TimeSeries, AnnotationQueryRequest, } from '@grafana/data'; -import InfluxSeries from './influx_series'; -import InfluxQueryModel from './influx_query_model'; -import ResponseParser from './response_parser'; -import { InfluxQueryBuilder } from './query_builder'; -import { InfluxOptions, InfluxQuery, InfluxVersion } from './types'; -import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -import { FluxQueryEditor } from './components/FluxQueryEditor'; -import { buildRawQuery } from './queryUtils'; +import { + BackendDataSourceResponse, + DataSourceWithBackend, + FetchResponse, + frameToMetricFindValue, + getBackendSrv, +} from '@grafana/runtime'; import config from 'app/core/config'; +import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; + +import { FluxQueryEditor } from './components/FluxQueryEditor'; +import InfluxQueryModel from './influx_query_model'; +import InfluxSeries from './influx_series'; +import { buildRawQuery } from './queryUtils'; +import { InfluxQueryBuilder } from './query_builder'; +import ResponseParser from './response_parser'; +import { InfluxOptions, InfluxQuery, InfluxVersion } from './types'; // we detect the field type based on the value-array function getFieldType(values: unknown[]): FieldType { diff --git a/public/app/plugins/datasource/influxdb/influxQLMetadataQuery.ts b/public/app/plugins/datasource/influxdb/influxQLMetadataQuery.ts index c03a1b0d16b..edd35d3f0ac 100644 --- a/public/app/plugins/datasource/influxdb/influxQLMetadataQuery.ts +++ b/public/app/plugins/datasource/influxdb/influxQLMetadataQuery.ts @@ -1,6 +1,6 @@ -import { InfluxQueryTag } from './types'; import InfluxDatasource from './datasource'; import { InfluxQueryBuilder } from './query_builder'; +import { InfluxQueryTag } from './types'; const runExploreQuery = ( type: string, diff --git a/public/app/plugins/datasource/influxdb/influx_query_model.ts b/public/app/plugins/datasource/influxdb/influx_query_model.ts index 78f69e666af..cbb6a7ab98c 100644 --- a/public/app/plugins/datasource/influxdb/influx_query_model.ts +++ b/public/app/plugins/datasource/influxdb/influx_query_model.ts @@ -1,9 +1,11 @@ import { map, find, filter, indexOf } from 'lodash'; -import queryPart from './query_part'; -import kbn from 'app/core/utils/kbn'; -import { InfluxQuery, InfluxQueryTag } from './types'; + import { ScopedVars } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; +import kbn from 'app/core/utils/kbn'; + +import queryPart from './query_part'; +import { InfluxQuery, InfluxQueryTag } from './types'; export default class InfluxQueryModel { target: InfluxQuery; diff --git a/public/app/plugins/datasource/influxdb/influx_series.ts b/public/app/plugins/datasource/influxdb/influx_series.ts index c60d394d45e..2776592155b 100644 --- a/public/app/plugins/datasource/influxdb/influx_series.ts +++ b/public/app/plugins/datasource/influxdb/influx_series.ts @@ -1,6 +1,7 @@ import { each, map, includes, flatten, keys } from 'lodash'; -import TableModel from 'app/core/table_model'; + import { FieldType, QueryResultMeta, TimeSeries, TableData } from '@grafana/data'; +import TableModel from 'app/core/table_model'; export default class InfluxSeries { refId?: string; diff --git a/public/app/plugins/datasource/influxdb/module.ts b/public/app/plugins/datasource/influxdb/module.ts index 6c6dd21b6f4..24d9e767648 100644 --- a/public/app/plugins/datasource/influxdb/module.ts +++ b/public/app/plugins/datasource/influxdb/module.ts @@ -1,9 +1,10 @@ -import InfluxDatasource from './datasource'; -import { QueryEditor } from './components/QueryEditor'; -import InfluxStartPage from './components/InfluxStartPage'; import { DataSourcePlugin } from '@grafana/data'; + import ConfigEditor from './components/ConfigEditor'; +import InfluxStartPage from './components/InfluxStartPage'; +import { QueryEditor } from './components/QueryEditor'; import VariableQueryEditor from './components/VariableQueryEditor'; +import InfluxDatasource from './datasource'; class InfluxAnnotationsQueryCtrl { static templateUrl = 'partials/annotations.editor.html'; diff --git a/public/app/plugins/datasource/influxdb/queryUtils.test.ts b/public/app/plugins/datasource/influxdb/queryUtils.test.ts index 1dc9649a54b..531c5446705 100644 --- a/public/app/plugins/datasource/influxdb/queryUtils.test.ts +++ b/public/app/plugins/datasource/influxdb/queryUtils.test.ts @@ -1,6 +1,7 @@ import { cloneDeep } from 'lodash'; -import { InfluxQuery } from './types'; + import { buildRawQuery, normalizeQuery, changeSelectPart, changeGroupByPart } from './queryUtils'; +import { InfluxQuery } from './types'; describe('InfluxDB query utils', () => { describe('buildRawQuery', () => { diff --git a/public/app/plugins/datasource/influxdb/queryUtils.ts b/public/app/plugins/datasource/influxdb/queryUtils.ts index 9db6c905735..4e29614906c 100644 --- a/public/app/plugins/datasource/influxdb/queryUtils.ts +++ b/public/app/plugins/datasource/influxdb/queryUtils.ts @@ -1,4 +1,5 @@ import { cloneDeep } from 'lodash'; + import InfluxQueryModel from './influx_query_model'; import { InfluxQuery } from './types'; diff --git a/public/app/plugins/datasource/influxdb/query_builder.ts b/public/app/plugins/datasource/influxdb/query_builder.ts index 49b8a0089d5..ba044d436b6 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.ts +++ b/public/app/plugins/datasource/influxdb/query_builder.ts @@ -1,4 +1,5 @@ import { reduce } from 'lodash'; + import kbn from 'app/core/utils/kbn'; function renderTagCondition(tag: { operator: any; value: string; condition: any; key: string }, index: number) { diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 98d7cdc55f9..b8914ecabb8 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -1,4 +1,5 @@ import { map, clone } from 'lodash'; + import { QueryPartDef, QueryPart, functionRenderer, suffixRenderer } from 'app/features/alerting/state/query_part'; const index: any[] = []; diff --git a/public/app/plugins/datasource/influxdb/response_parser.ts b/public/app/plugins/datasource/influxdb/response_parser.ts index 93544aded31..459da8b441e 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.ts @@ -1,7 +1,9 @@ +import { each, flatten, groupBy, isArray } from 'lodash'; + import { AnnotationEvent, DataFrame, DataQuery, FieldType, QueryResultMeta } from '@grafana/data'; import { toDataQueryResponse } from '@grafana/runtime'; import TableModel from 'app/core/table_model'; -import { each, flatten, groupBy, isArray } from 'lodash'; + import { InfluxQuery } from './types'; export default class ResponseParser { diff --git a/public/app/plugins/datasource/influxdb/specs/datasource.test.ts b/public/app/plugins/datasource/influxdb/specs/datasource.test.ts index eb3fcc2e556..604e9c53a36 100644 --- a/public/app/plugins/datasource/influxdb/specs/datasource.test.ts +++ b/public/app/plugins/datasource/influxdb/specs/datasource.test.ts @@ -1,10 +1,11 @@ import { lastValueFrom, of } from 'rxjs'; +import { TemplateSrvStub } from 'test/specs/helpers'; + import { FetchResponse } from '@grafana/runtime'; +import config from 'app/core/config'; +import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import InfluxDatasource from '../datasource'; -import { TemplateSrvStub } from 'test/specs/helpers'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ -import config from 'app/core/config'; //@ts-ignore const templateSrv = new TemplateSrvStub(); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_series.test.ts b/public/app/plugins/datasource/influxdb/specs/influx_series.test.ts index d65b92916bd..ade1587f87e 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_series.test.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_series.test.ts @@ -1,4 +1,5 @@ import produce from 'immer'; + import InfluxSeries from '../influx_series'; describe('when generating timeseries from influxdb response', () => { diff --git a/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts b/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts index b9f988c497c..4ce76c6fa05 100644 --- a/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts +++ b/public/app/plugins/datasource/influxdb/specs/response_parser.test.ts @@ -1,13 +1,15 @@ import { size } from 'lodash'; -import ResponseParser, { getSelectedParams } from '../response_parser'; -import InfluxQueryModel from '../influx_query_model'; -import { FieldType, MutableDataFrame } from '@grafana/data'; -import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ -import InfluxDatasource from '../datasource'; import { of } from 'rxjs'; -import { FetchResponse } from '@grafana/runtime'; import { TemplateSrvStub } from 'test/specs/helpers'; + +import { FieldType, MutableDataFrame } from '@grafana/data'; +import { FetchResponse } from '@grafana/runtime'; import config from 'app/core/config'; +import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ + +import InfluxDatasource from '../datasource'; +import InfluxQueryModel from '../influx_query_model'; +import ResponseParser, { getSelectedParams } from '../response_parser'; //@ts-ignore const templateSrv = new TemplateSrvStub(); diff --git a/public/app/plugins/datasource/jaeger/components/AdvancedOptions.tsx b/public/app/plugins/datasource/jaeger/components/AdvancedOptions.tsx index dff7cc564fc..ff7c497ea95 100644 --- a/public/app/plugins/datasource/jaeger/components/AdvancedOptions.tsx +++ b/public/app/plugins/datasource/jaeger/components/AdvancedOptions.tsx @@ -1,8 +1,10 @@ import { css } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; -import { Icon, InlineField, InlineFieldRow, InlineLabel, Input, useStyles } from '@grafana/ui'; import React, { useState } from 'react'; import { CSSTransition } from 'react-transition-group'; + +import { GrafanaTheme } from '@grafana/data'; +import { Icon, InlineField, InlineFieldRow, InlineLabel, Input, useStyles } from '@grafana/ui'; + import { JaegerQuery } from '../types'; const durationPlaceholder = 'e.g. 1.2s, 100ms, 500us'; diff --git a/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx b/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx index 7ad0fe8ead0..114a7ef8a3e 100644 --- a/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx @@ -1,8 +1,9 @@ +import React from 'react'; + import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { DataSourceHttpSettings } from '@grafana/ui'; import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; -import React from 'react'; export type Props = DataSourcePluginOptionsEditorProps; diff --git a/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx b/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx index 2a2ed26ddb7..29de17cf6d6 100644 --- a/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx @@ -1,9 +1,12 @@ import { css } from '@emotion/css'; +import React from 'react'; + import { QueryEditorProps } from '@grafana/data'; import { FileDropzone, InlineField, InlineFieldRow, QueryField, RadioButtonGroup, useTheme2 } from '@grafana/ui'; -import React from 'react'; + import { JaegerDatasource } from '../datasource'; import { JaegerQuery, JaegerQueryType } from '../types'; + import { SearchForm } from './SearchForm'; type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/jaeger/components/SearchForm.test.tsx b/public/app/plugins/datasource/jaeger/components/SearchForm.test.tsx index 77d85ac8526..00171650969 100644 --- a/public/app/plugins/datasource/jaeger/components/SearchForm.test.tsx +++ b/public/app/plugins/datasource/jaeger/components/SearchForm.test.tsx @@ -1,14 +1,18 @@ -import { act, render, screen, waitFor } from '@testing-library/react'; -import { backendSrv } from 'app/core/services/backend_srv'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; -import { DataQueryRequest, DataSourceInstanceSettings, dateTime, PluginType } from '@grafana/data'; -import { of } from 'rxjs'; -import { JaegerDatasource, JaegerJsonData } from '../datasource'; -import { JaegerQuery } from '../types'; -import React from 'react'; -import SearchForm from './SearchForm'; -import { testResponse } from '../testResponse'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/dist/types/setup'; +import React from 'react'; +import { of } from 'rxjs'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; + +import { DataQueryRequest, DataSourceInstanceSettings, dateTime, PluginType } from '@grafana/data'; +import { backendSrv } from 'app/core/services/backend_srv'; + +import { JaegerDatasource, JaegerJsonData } from '../datasource'; +import { testResponse } from '../testResponse'; +import { JaegerQuery } from '../types'; + +import SearchForm from './SearchForm'; describe('SearchForm', () => { it('should call the `onChange` function on click of the Input', async () => { @@ -38,7 +42,7 @@ describe('SearchForm', () => { const asyncServiceSelect = await waitFor(() => screen.getByRole('combobox', { name: 'select-service-name' })); expect(asyncServiceSelect).toBeInTheDocument(); - userEvent.click(asyncServiceSelect); + await userEvent.click(asyncServiceSelect); const jaegerService = await screen.findByText('jaeger-query'); expect(jaegerService).toBeInTheDocument(); @@ -68,13 +72,21 @@ describe('SearchForm', () => { }); describe('SearchForm', () => { + let user: UserEvent; + + beforeEach(() => { + jest.useFakeTimers(); + // Need to use delay: null here to work with fakeTimers + // see https://github.com/testing-library/user-event/issues/833 + user = userEvent.setup({ delay: null }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should show loader if there is a delay fetching options', async () => { - const promise = Promise.resolve(); - const handleOnChange = jest.fn(() => { - setTimeout(() => { - return promise; - }, 3000); - }); + const handleOnChange = jest.fn(); const query = { ...defaultQuery, targets: [ @@ -91,12 +103,19 @@ describe('SearchForm', () => { render(); + jest.spyOn(ds, 'metadataRequest').mockImplementation(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(['jaeger-query']); + }, 3000); + }); + }); const asyncServiceSelect = screen.getByRole('combobox', { name: 'select-service-name' }); - userEvent.click(asyncServiceSelect); - const loader = screen.getByText('Loading options...'); + await user.click(asyncServiceSelect); + expect(screen.getByText('Loading options...')).toBeInTheDocument(); - expect(loader).toBeInTheDocument(); - await act(() => promise); + jest.advanceTimersByTime(3000); + await waitFor(() => expect(screen.queryByText('Loading options...')).not.toBeInTheDocument()); }); }); diff --git a/public/app/plugins/datasource/jaeger/components/SearchForm.tsx b/public/app/plugins/datasource/jaeger/components/SearchForm.tsx index c362314bbed..22d563ad651 100644 --- a/public/app/plugins/datasource/jaeger/components/SearchForm.tsx +++ b/public/app/plugins/datasource/jaeger/components/SearchForm.tsx @@ -1,14 +1,17 @@ import { css } from '@emotion/css'; +import React, { useCallback, useEffect, useState } from 'react'; + import { SelectableValue } from '@grafana/data'; import { AsyncSelect, InlineField, InlineFieldRow, Input } from '@grafana/ui'; -import React, { useCallback, useEffect, useState } from 'react'; +import { notifyApp } from 'app/core/actions'; +import { createErrorNotification } from 'app/core/copy/appNotification'; +import { dispatch } from 'app/store/store'; + import { JaegerDatasource } from '../datasource'; import { JaegerQuery } from '../types'; import { transformToLogfmt } from '../util'; + import { AdvancedOptions } from './AdvancedOptions'; -import { dispatch } from 'app/store/store'; -import { notifyApp } from 'app/core/actions'; -import { createErrorNotification } from 'app/core/copy/appNotification'; type Props = { datasource: JaegerDatasource; diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index 6d7284f635c..f9af99e4de5 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -1,4 +1,6 @@ import { lastValueFrom, of, throwError } from 'rxjs'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; + import { DataQueryRequest, DataSourceInstanceSettings, @@ -7,9 +9,8 @@ import { PluginType, ScopedVars, } from '@grafana/data'; - import { backendSrv } from 'app/core/services/backend_srv'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; + import { ALL_OPERATIONS_KEY } from './components/SearchForm'; import { JaegerDatasource, JaegerJsonData } from './datasource'; import mockJson from './mockJsonResponse.json'; diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 83581fcc816..d042b271f23 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -1,6 +1,7 @@ import { identity, omit, pick, pickBy } from 'lodash'; import { lastValueFrom, Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; + import { DataQueryRequest, DataQueryResponse, @@ -13,15 +14,15 @@ import { MutableDataFrame, } from '@grafana/data'; import { BackendSrvRequest, getBackendSrv, getTemplateSrv } from '@grafana/runtime'; - +import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings'; import { serializeParams } from 'app/core/utils/fetch'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { createTableFrame, createTraceFrame } from './responseTransform'; + +import { ALL_OPERATIONS_KEY } from './components/SearchForm'; import { createGraphFrames } from './graphTransform'; +import { createTableFrame, createTraceFrame } from './responseTransform'; import { JaegerQuery } from './types'; import { convertTagsLogfmt } from './util'; -import { ALL_OPERATIONS_KEY } from './components/SearchForm'; -import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings'; export interface JaegerJsonData extends DataSourceJsonData { nodeGraph?: NodeGraphOptions; diff --git a/public/app/plugins/datasource/jaeger/graphTransform.ts b/public/app/plugins/datasource/jaeger/graphTransform.ts index ae866449687..357b36774ba 100644 --- a/public/app/plugins/datasource/jaeger/graphTransform.ts +++ b/public/app/plugins/datasource/jaeger/graphTransform.ts @@ -1,7 +1,9 @@ import { DataFrame, NodeGraphDataFrameFieldNames as Fields } from '@grafana/data'; -import { Span, TraceResponse } from './types'; + import { getNonOverlappingDuration, getStats, makeFrames, makeSpanMap } from '../../../core/utils/tracing'; +import { Span, TraceResponse } from './types'; + interface Node { [Fields.id]: string; [Fields.title]: string; diff --git a/public/app/plugins/datasource/jaeger/module.ts b/public/app/plugins/datasource/jaeger/module.ts index d81c4807cc4..07dca616b63 100644 --- a/public/app/plugins/datasource/jaeger/module.ts +++ b/public/app/plugins/datasource/jaeger/module.ts @@ -1,6 +1,7 @@ import { DataSourcePlugin } from '@grafana/data'; -import { JaegerDatasource } from './datasource'; -import { QueryEditor } from './components/QueryEditor'; + import { ConfigEditor } from './components/ConfigEditor'; +import { QueryEditor } from './components/QueryEditor'; +import { JaegerDatasource } from './datasource'; export const plugin = new DataSourcePlugin(JaegerDatasource).setConfigEditor(ConfigEditor).setQueryEditor(QueryEditor); diff --git a/public/app/plugins/datasource/jaeger/responseTransform.test.ts b/public/app/plugins/datasource/jaeger/responseTransform.test.ts index b24d01f7a3b..24cab644e37 100644 --- a/public/app/plugins/datasource/jaeger/responseTransform.test.ts +++ b/public/app/plugins/datasource/jaeger/responseTransform.test.ts @@ -1,6 +1,7 @@ +import { MutableDataFrame } from '@grafana/data'; + import { createTraceFrame, transformToJaeger } from './responseTransform'; import { testResponse, testResponseDataFrameFields } from './testResponse'; -import { MutableDataFrame } from '@grafana/data'; describe('createTraceFrame', () => { it('creates data frame from jaeger response', () => { diff --git a/public/app/plugins/datasource/jaeger/responseTransform.ts b/public/app/plugins/datasource/jaeger/responseTransform.ts index f15763f1ec6..4e6e8683aa7 100644 --- a/public/app/plugins/datasource/jaeger/responseTransform.ts +++ b/public/app/plugins/datasource/jaeger/responseTransform.ts @@ -7,6 +7,7 @@ import { TraceSpanRow, } from '@grafana/data'; import { transformTraceData } from '@jaegertracing/jaeger-ui-components'; + import { JaegerResponse, Span, TraceProcess, TraceResponse } from './types'; export function createTraceFrame(data: TraceResponse): DataFrame { diff --git a/public/app/plugins/datasource/jaeger/testResponse.ts b/public/app/plugins/datasource/jaeger/testResponse.ts index e9627249eae..026de7cbc44 100644 --- a/public/app/plugins/datasource/jaeger/testResponse.ts +++ b/public/app/plugins/datasource/jaeger/testResponse.ts @@ -1,6 +1,7 @@ -import { TraceResponse } from './types'; import { ArrayVector, FieldDTO } from '@grafana/data'; +import { TraceResponse } from './types'; + export const testResponse: TraceResponse = { traceID: '3fa414edcef6ad90', spans: [ diff --git a/public/app/plugins/datasource/loki/add_label_to_query.ts b/public/app/plugins/datasource/loki/add_label_to_query.ts index 5d345703438..d4e6c1a62bd 100644 --- a/public/app/plugins/datasource/loki/add_label_to_query.ts +++ b/public/app/plugins/datasource/loki/add_label_to_query.ts @@ -1,7 +1,9 @@ import { chain, isEqual } from 'lodash'; -import { LOKI_KEYWORDS } from './syntax'; + import { PROM_KEYWORDS, OPERATORS, LOGICAL_OPERATORS } from 'app/plugins/datasource/prometheus/promql'; +import { LOKI_KEYWORDS } from './syntax'; + const builtInWords = [...PROM_KEYWORDS, ...OPERATORS, ...LOGICAL_OPERATORS, ...LOKI_KEYWORDS]; // We want to extract all possible metrics and also keywords diff --git a/public/app/plugins/datasource/loki/backendResultTransformer.test.ts b/public/app/plugins/datasource/loki/backendResultTransformer.test.ts index f4fb69f3db4..ec4ab229322 100644 --- a/public/app/plugins/datasource/loki/backendResultTransformer.test.ts +++ b/public/app/plugins/datasource/loki/backendResultTransformer.test.ts @@ -1,6 +1,7 @@ -import { ArrayVector, DataFrame, DataQueryResponse, FieldType } from '@grafana/data'; import { cloneDeep } from 'lodash'; +import { ArrayVector, DataFrame, DataQueryResponse, FieldType } from '@grafana/data'; + import { transformBackendResult } from './backendResultTransformer'; const LOKI_EXPR = '{level="info"} |= "thing1"'; diff --git a/public/app/plugins/datasource/loki/backendResultTransformer.ts b/public/app/plugins/datasource/loki/backendResultTransformer.ts index ba4a6b3ebf0..2422359c74e 100644 --- a/public/app/plugins/datasource/loki/backendResultTransformer.ts +++ b/public/app/plugins/datasource/loki/backendResultTransformer.ts @@ -7,9 +7,10 @@ import { ArrayVector, Labels, } from '@grafana/data'; -import { LokiQuery, LokiQueryType } from './types'; + import { makeTableFrames } from './makeTableFrames'; import { formatQuery, getHighlighterExpressionsFromQuery } from './query_utils'; +import { LokiQuery, LokiQueryType } from './types'; function isMetricFrame(frame: DataFrame): boolean { return frame.fields.every((field) => field.type === FieldType.time || field.type === FieldType.number); diff --git a/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx b/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx index 5883726011c..babf897af12 100644 --- a/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx @@ -1,10 +1,12 @@ // Libraries import React, { memo } from 'react'; + // Types -import { LokiQuery } from '../types'; -import { LokiQueryField } from './LokiQueryField'; -import { LokiOptionFields } from './LokiOptionFields'; import { LokiDatasource } from '../datasource'; +import { LokiQuery } from '../types'; + +import { LokiOptionFields } from './LokiOptionFields'; +import { LokiQueryField } from './LokiQueryField'; interface Props { expr: string; diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index 924f12b52e9..cead9587a9c 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -1,6 +1,8 @@ -import React, { PureComponent } from 'react'; import { shuffle } from 'lodash'; +import React, { PureComponent } from 'react'; + import { QueryEditorHelpProps } from '@grafana/data'; + import LokiLanguageProvider from '../language_provider'; import { LokiQuery } from '../types'; diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx index c8fbc9e6fdb..d2ee8a6c956 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx @@ -1,13 +1,16 @@ -import React from 'react'; import { mount, shallow } from 'enzyme'; +import React from 'react'; import { act } from 'react-dom/test-utils'; + +import { ExploreMode, LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data'; + +import { LokiDatasource } from '../datasource'; +import LokiLanguageProvider from '../language_provider'; +import { makeMockLokiDatasource } from '../mocks'; +import { LokiQuery } from '../types'; + import { LokiExploreQueryEditor } from './LokiExploreQueryEditor'; import { LokiOptionFields } from './LokiOptionFields'; -import { LokiDatasource } from '../datasource'; -import { LokiQuery } from '../types'; -import { ExploreMode, LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data'; -import { makeMockLokiDatasource } from '../mocks'; -import LokiLanguageProvider from '../language_provider'; const setup = (renderMethod: any, propOverrides?: object) => { const datasource: LokiDatasource = makeMockLokiDatasource({}); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx index c78ef98047d..6fcfde57716 100644 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx @@ -3,10 +3,12 @@ import React, { memo } from 'react'; // Types import { QueryEditorProps } from '@grafana/data'; + import { LokiDatasource } from '../datasource'; import { LokiQuery, LokiOptions } from '../types'; -import { LokiQueryField } from './LokiQueryField'; + import { LokiOptionFields } from './LokiOptionFields'; +import { LokiQueryField } from './LokiQueryField'; type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/loki/components/LokiLabelBrowser.test.tsx b/public/app/plugins/datasource/loki/components/LokiLabelBrowser.test.tsx index f64f2df2634..06f745f7482 100644 --- a/public/app/plugins/datasource/loki/components/LokiLabelBrowser.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiLabelBrowser.test.tsx @@ -1,8 +1,11 @@ -import React from 'react'; -import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { createTheme } from '@grafana/data'; + +import LokiLanguageProvider from '../language_provider'; + import { buildSelector, facetLabels, @@ -10,7 +13,6 @@ import { UnthemedLokiLabelBrowser, BrowserProps, } from './LokiLabelBrowser'; -import LokiLanguageProvider from '../language_provider'; describe('buildSelector()', () => { it('returns an empty selector for no labels', () => { @@ -118,9 +120,9 @@ describe('LokiLabelBrowser', () => { }; // Clear label selection manually because it's saved in localStorage - afterEach(() => { + afterEach(async () => { const clearBtn = screen.getByLabelText('Selector clear button'); - userEvent.click(clearBtn); + await userEvent.click(clearBtn); }); it('renders and loader shows when empty, and then first set of labels', async () => { @@ -143,10 +145,10 @@ describe('LokiLabelBrowser', () => { const props = setupProps(); render(); // Selecting label2 - const label2 = await screen.findByRole('option', { name: /label2/, selected: false }); + const label2 = await screen.findByRole('option', { name: 'label2', selected: false }); expect(screen.queryByRole('list', { name: /Values/ })).not.toBeInTheDocument(); - userEvent.click(label2); - expect(screen.queryByRole('option', { name: /label2/, selected: true })).toBeInTheDocument(); + await userEvent.click(label2); + expect(screen.queryByRole('option', { name: 'label2', selected: true })).toBeInTheDocument(); // List of values for label2 appears expect(await screen.findAllByRole('list')).toHaveLength(1); expect(screen.queryByLabelText(/Values for/)).toHaveTextContent('label2'); @@ -154,24 +156,24 @@ describe('LokiLabelBrowser', () => { expect(screen.queryByRole('option', { name: 'value2-2' })).toBeInTheDocument(); expect(screen.queryByLabelText('selector')).toHaveTextContent('{}'); // Selecting label1, list for its values appears - const label1 = await screen.findByRole('option', { name: /label1/, selected: false }); - userEvent.click(label1); - expect(screen.queryByRole('option', { name: /label1/, selected: true })).toBeInTheDocument(); + const label1 = await screen.findByRole('option', { name: 'label1', selected: false }); + await userEvent.click(label1); + expect(screen.queryByRole('option', { name: 'label1', selected: true })).toBeInTheDocument(); await screen.findByLabelText('Values for label1'); expect(await screen.findAllByRole('list')).toHaveLength(2); // Selecting value2-2 of label2 const value = await screen.findByRole('option', { name: 'value2-2', selected: false }); - userEvent.click(value); + await userEvent.click(value); await screen.findByRole('option', { name: 'value2-2', selected: true }); expect(screen.queryByLabelText('selector')).toHaveTextContent('{label2="value2-2"}'); // Selecting value2-1 of label2, both values now selected const value2 = await screen.findByRole('option', { name: 'value2-1', selected: false }); - userEvent.click(value2); + await userEvent.click(value2); // await screen.findByRole('option', {name: 'value2-1', selected: true}); await screen.findByText('{label2=~"value2-1|value2-2"}'); // Deselecting value2-2, one value should remain const selectedValue = await screen.findByRole('option', { name: 'value2-2', selected: true }); - userEvent.click(selectedValue); + await userEvent.click(selectedValue); await screen.findByRole('option', { name: 'value2-1', selected: true }); await screen.findByRole('option', { name: 'value2-2', selected: false }); expect(screen.queryByLabelText('selector')).toHaveTextContent('{label2="value2-1"}'); @@ -183,28 +185,30 @@ describe('LokiLabelBrowser', () => { // Selecting label2 const label2 = await screen.findByRole('option', { name: /label2/, selected: false }); - userEvent.click(label2); + await userEvent.click(label2); // List of values for label2 appears - expect(await screen.findAllByRole('list')).toHaveLength(1); + expect(screen.queryByLabelText(/Values for/)).toHaveTextContent('label2'); + expect(screen.queryByRole('option', { name: 'value2-1' })).toBeInTheDocument(); + expect(screen.queryByRole('option', { name: 'value2-2' })).toBeInTheDocument(); expect(screen.queryByLabelText('selector')).toHaveTextContent('{}'); // Selecting label1, list for its values appears - const label1 = await screen.findByRole('option', { name: /label1/, selected: false }); - userEvent.click(label1); + const label1 = await screen.findByRole('option', { name: 'label1', selected: false }); + await userEvent.click(label1); await screen.findByLabelText('Values for label1'); expect(await screen.findAllByRole('list')).toHaveLength(2); // Selecting value2-1 of label2 const value2 = await screen.findByRole('option', { name: 'value2-1', selected: false }); - userEvent.click(value2); + await userEvent.click(value2); await screen.findByText('{label2="value2-1"}'); // Selecting value from label1 for combined selector const value1 = await screen.findByRole('option', { name: 'value1-2', selected: false }); - userEvent.click(value1); + await userEvent.click(value1); await screen.findByRole('option', { name: 'value1-2', selected: true }); await screen.findByText('{label1="value1-2",label2="value2-1"}'); // Deselect label1 should remove label and value const selectedLabel = (await screen.findAllByRole('option', { name: /label1/, selected: true }))[0]; - userEvent.click(selectedLabel); + await userEvent.click(selectedLabel); await screen.findByRole('option', { name: /label1/, selected: false }); expect(await screen.findAllByRole('list')).toHaveLength(1); expect(screen.queryByLabelText('selector')).toHaveTextContent('{label2="value2-1"}'); @@ -215,25 +219,27 @@ describe('LokiLabelBrowser', () => { render(); // Selecting label2 - const label2 = await screen.findByRole('option', { name: /label2/, selected: false }); - userEvent.click(label2); + const label2 = await screen.findByRole('option', { name: 'label2', selected: false }); + await userEvent.click(label2); // List of values for label2 appears - expect(await screen.findAllByRole('list')).toHaveLength(1); + expect(screen.queryByLabelText(/Values for/)).toHaveTextContent('label2'); + expect(screen.queryByRole('option', { name: 'value2-1' })).toBeInTheDocument(); + expect(screen.queryByRole('option', { name: 'value2-2' })).toBeInTheDocument(); expect(screen.queryByLabelText('selector')).toHaveTextContent('{}'); // Selecting label1, list for its values appears - const label1 = await screen.findByRole('option', { name: /label1/, selected: false }); - userEvent.click(label1); + const label1 = await screen.findByRole('option', { name: 'label1', selected: false }); + await userEvent.click(label1); await screen.findByLabelText('Values for label1'); expect(await screen.findAllByRole('list')).toHaveLength(2); // Selecting value2-1 of label2 const value2 = await screen.findByRole('option', { name: 'value2-1', selected: false }); - userEvent.click(value2); + await userEvent.click(value2); await screen.findByText('{label2="value2-1"}'); // Clear selector const clearBtn = screen.getByLabelText('Selector clear button'); - userEvent.click(clearBtn); - await screen.findByRole('option', { name: /label2/, selected: false }); + await userEvent.click(clearBtn); + await screen.findByRole('option', { name: 'label2', selected: false }); expect(screen.queryByLabelText('selector')).toHaveTextContent('{}'); }); @@ -242,14 +248,14 @@ describe('LokiLabelBrowser', () => { render(); // Selecting label2 and label1 const label2 = await screen.findByRole('option', { name: /label2/, selected: false }); - userEvent.click(label2); + await userEvent.click(label2); const label1 = await screen.findByRole('option', { name: /label1/, selected: false }); - userEvent.click(label1); + await userEvent.click(label1); await screen.findByLabelText('Values for label1'); await screen.findByLabelText('Values for label2'); expect(await screen.findAllByRole('option', { name: /value/ })).toHaveLength(4); // Typing '1' to filter for values - userEvent.type(screen.getByLabelText('Filter expression for values'), 'val1'); + await userEvent.type(screen.getByLabelText('Filter expression for values'), 'val1'); expect(screen.getByLabelText('Filter expression for values')).toHaveValue('val1'); expect(screen.queryByRole('option', { name: 'value2-2' })).not.toBeInTheDocument(); expect(await screen.findAllByRole('option', { name: /value/ })).toHaveLength(3); @@ -260,25 +266,25 @@ describe('LokiLabelBrowser', () => { render(); // Selecting label2 and label1 const label2 = await screen.findByRole('option', { name: /label2/, selected: false }); - userEvent.click(label2); + await userEvent.click(label2); const label1 = await screen.findByRole('option', { name: /label1/, selected: false }); - userEvent.click(label1); + await userEvent.click(label1); await screen.findByLabelText('Values for label1'); await screen.findByLabelText('Values for label2'); expect(await screen.findAllByRole('option', { name: /value/ })).toHaveLength(4); expect(screen.queryByRole('option', { name: /label3/ })).toHaveTextContent('label3'); // Click value1-1 which triggers facetting for value3-x, and still show all value1-x const value1 = await screen.findByRole('option', { name: 'value1-1', selected: false }); - userEvent.click(value1); - await waitForElementToBeRemoved(screen.queryByRole('option', { name: 'value2-2' })); + await userEvent.click(value1); + await waitFor(() => expect(screen.queryByRole('option', { name: 'value2-2' })).not.toBeInTheDocument()); expect(screen.queryByRole('option', { name: 'value1-2' })).toBeInTheDocument(); expect(screen.queryByLabelText('selector')).toHaveTextContent('{label1="value1-1"}'); expect(screen.queryByRole('option', { name: /label3/ })).toHaveTextContent('label3 (1)'); // Click value1-2 for which facetting will allow more values for value3-x const value12 = await screen.findByRole('option', { name: 'value1-2', selected: false }); - userEvent.click(value12); + await userEvent.click(value12); await screen.findByRole('option', { name: 'value1-2', selected: true }); - userEvent.click(screen.getByRole('option', { name: /label3/ })); + await userEvent.click(screen.getByRole('option', { name: /label3/ })); await screen.findByLabelText('Values for label3'); expect(screen.queryByRole('option', { name: 'value1-1', selected: true })).toBeInTheDocument(); expect(screen.queryByRole('option', { name: 'value1-2', selected: true })).toBeInTheDocument(); diff --git a/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx b/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx index ca5f45dfeaf..d5ff9246194 100644 --- a/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx +++ b/public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx @@ -1,4 +1,9 @@ +import { css, cx } from '@emotion/css'; +import { sortBy } from 'lodash'; import React, { ChangeEvent } from 'react'; +import { FixedSizeList } from 'react-window'; + +import { GrafanaTheme2 } from '@grafana/data'; import { Button, HighlightPart, @@ -10,12 +15,9 @@ import { BrowserLabel as LokiLabel, fuzzyMatch, } from '@grafana/ui'; -import LokiLanguageProvider from '../language_provider'; + import PromQlLanguageProvider from '../../prometheus/language_provider'; -import { css, cx } from '@emotion/css'; -import { FixedSizeList } from 'react-window'; -import { GrafanaTheme2 } from '@grafana/data'; -import { sortBy } from 'lodash'; +import LokiLanguageProvider from '../language_provider'; // Hard limit on labels to render const MAX_LABEL_COUNT = 1000; diff --git a/public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx b/public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx index c91d0546c89..89e51d201cd 100644 --- a/public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiOptionFields.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { LokiOptionFieldsProps, LokiOptionFields } from './LokiOptionFields'; const setup = (propOverrides?: LokiOptionFieldsProps) => { diff --git a/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx b/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx index a997c94760b..d0bab159be0 100644 --- a/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx +++ b/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx @@ -1,12 +1,13 @@ // Libraries -import React, { memo } from 'react'; import { css, cx } from '@emotion/css'; import { map } from 'lodash'; +import React, { memo } from 'react'; // Types -import { InlineFormLabel, RadioButtonGroup, InlineField, Input, Select } from '@grafana/ui'; import { SelectableValue } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { InlineFormLabel, RadioButtonGroup, InlineField, Input, Select } from '@grafana/ui'; + import { LokiQuery, LokiQueryType } from '../types'; export interface LokiOptionFieldsProps { diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx index 53485f9db95..b1d5caa1188 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { shallow } from 'enzyme'; +import React from 'react'; + import { toUtc } from '@grafana/data'; -import { LokiQueryEditor } from './LokiQueryEditor'; import { LokiDatasource } from '../datasource'; import { LokiQuery } from '../types'; +import { LokiQueryEditor } from './LokiQueryEditor'; + const createMockRequestRange = (from: string, to: string) => { return { from: toUtc(from, 'YYYY-MM-DD'), diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index 5aa42a64dcb..3e2aff5db27 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -3,8 +3,9 @@ import React from 'react'; // Types import { InlineFormLabel } from '@grafana/ui'; -import { LokiQueryField } from './LokiQueryField'; + import { LokiOptionFields } from './LokiOptionFields'; +import { LokiQueryField } from './LokiQueryField'; import { LokiQueryEditorProps } from './types'; export function LokiQueryEditor(props: LokiQueryEditorProps) { diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx index 65e2bdd558e..e52790a9817 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { render, RenderResult } from '@testing-library/react'; -import { CoreApp } from '@grafana/data'; import { noop } from 'lodash'; +import React from 'react'; + +import { CoreApp } from '@grafana/data'; + import { LokiDatasource } from '../datasource'; -import { testIds as alertingTestIds } from './LokiQueryEditorForAlerting'; -import { testIds as regularTestIds } from './LokiQueryEditor'; + import { testIds as exploreTestIds } from './LokiExploreQueryEditor'; +import { testIds as regularTestIds } from './LokiQueryEditor'; import { LokiQueryEditorByApp } from './LokiQueryEditorByApp'; +import { testIds as alertingTestIds } from './LokiQueryEditorForAlerting'; function setup(app: CoreApp): RenderResult { const dataSource = { diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx index e49db26d3e1..bd3b6c91654 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx @@ -1,11 +1,14 @@ import React, { memo } from 'react'; + import { CoreApp } from '@grafana/data'; -import { LokiQueryEditorProps } from './types'; +import { config } from '@grafana/runtime'; + +import { LokiQueryEditorSelector } from '../querybuilder/components/LokiQueryEditorSelector'; + +import { LokiExploreQueryEditor } from './LokiExploreQueryEditor'; import { LokiQueryEditor } from './LokiQueryEditor'; import { LokiQueryEditorForAlerting } from './LokiQueryEditorForAlerting'; -import { LokiExploreQueryEditor } from './LokiExploreQueryEditor'; -import { LokiQueryEditorSelector } from '../querybuilder/components/LokiQueryEditorSelector'; -import { config } from '@grafana/runtime'; +import { LokiQueryEditorProps } from './types'; export function LokiQueryEditorByApp(props: LokiQueryEditorProps) { const { app } = props; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditorForAlerting.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditorForAlerting.tsx index c7878af05a3..a7a77698071 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditorForAlerting.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditorForAlerting.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { LokiQueryField } from './LokiQueryField'; import { LokiQueryEditorProps } from './types'; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx index 19be0fbaec1..61abf95b8e1 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx @@ -1,8 +1,10 @@ -import React, { ComponentProps } from 'react'; import { render } from '@testing-library/react'; -import { LokiQueryField } from './LokiQueryField'; +import React, { ComponentProps } from 'react'; + import { dateTime } from '@grafana/data'; +import { LokiQueryField } from './LokiQueryField'; + type Props = ComponentProps; const defaultProps: Props = { diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 6e139928c6b..1be894153a0 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -1,4 +1,8 @@ +import { LanguageMap, languages as prismLanguages } from 'prismjs'; import React, { ReactNode } from 'react'; +import { Plugin, Node } from 'slate'; + +import { QueryEditorProps } from '@grafana/data'; import { SlatePrism, TypeaheadOutput, @@ -9,15 +13,14 @@ import { DOMUtil, Icon, } from '@grafana/ui'; -import { Plugin, Node } from 'slate'; -import { LokiLabelBrowser } from './LokiLabelBrowser'; -import { QueryEditorProps } from '@grafana/data'; -import { LokiQuery, LokiOptions } from '../types'; -import { LanguageMap, languages as prismLanguages } from 'prismjs'; +import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider'; + +import { LokiDatasource } from '../datasource'; import LokiLanguageProvider from '../language_provider'; import { shouldRefreshLabels } from '../language_utils'; -import { LokiDatasource } from '../datasource'; -import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider'; +import { LokiQuery, LokiOptions } from '../types'; + +import { LokiLabelBrowser } from './LokiLabelBrowser'; const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels'; diff --git a/public/app/plugins/datasource/loki/components/types.ts b/public/app/plugins/datasource/loki/components/types.ts index 95a671428f5..afea710b1b9 100644 --- a/public/app/plugins/datasource/loki/components/types.ts +++ b/public/app/plugins/datasource/loki/components/types.ts @@ -1,4 +1,5 @@ import { QueryEditorProps } from '@grafana/data'; + import { LokiDatasource } from '../datasource'; import { LokiOptions, LokiQuery } from '../types'; diff --git a/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx b/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx index 39f2a3bcf07..5aa387d9555 100644 --- a/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx @@ -1,8 +1,11 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { ConfigEditor } from './ConfigEditor'; -import { createDefaultConfigOptions } from '../mocks'; +import React from 'react'; + import { DataSourceHttpSettings } from '@grafana/ui'; + +import { createDefaultConfigOptions } from '../mocks'; + +import { ConfigEditor } from './ConfigEditor'; import { DerivedFields } from './DerivedFields'; describe('ConfigEditor', () => { diff --git a/public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx index 82d841b4540..f2d58e33821 100644 --- a/public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx @@ -1,11 +1,14 @@ import React from 'react'; + import { DataSourcePluginOptionsEditorProps, DataSourceSettings } from '@grafana/data'; import { AlertingSettings, DataSourceHttpSettings } from '@grafana/ui'; -import { LokiOptions } from '../types'; -import { MaxLinesField } from './MaxLinesField'; -import { DerivedFields } from './DerivedFields'; import { getAllAlertmanagerDataSources } from 'app/features/alerting/unified/utils/alertmanager'; +import { LokiOptions } from '../types'; + +import { DerivedFields } from './DerivedFields'; +import { MaxLinesField } from './MaxLinesField'; + export type Props = DataSourcePluginOptionsEditorProps; const makeJsonUpdater = diff --git a/public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx b/public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx index d1aaac6c77a..82d6b2064ea 100644 --- a/public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx @@ -1,11 +1,13 @@ -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; + import { dateTime, TimeRange } from '@grafana/data'; import { setTemplateSrv } from '@grafana/runtime'; -import { DebugSection } from './DebugSection'; import { getLinkSrv, LinkService, LinkSrv, setLinkSrv } from '../../../../features/panel/panellinks/link_srv'; +import { DebugSection } from './DebugSection'; + // We do not need more here and TimeSrv is hard to setup fully. jest.mock('app/features/dashboard/services/TimeSrv', () => ({ getTimeSrv: () => ({ diff --git a/public/app/plugins/datasource/loki/configuration/DebugSection.tsx b/public/app/plugins/datasource/loki/configuration/DebugSection.tsx index 580847b4f76..49f8c958e5e 100644 --- a/public/app/plugins/datasource/loki/configuration/DebugSection.tsx +++ b/public/app/plugins/datasource/loki/configuration/DebugSection.tsx @@ -1,11 +1,14 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; import cx from 'classnames'; -import { LegacyForms } from '@grafana/ui'; -const { FormField } = LegacyForms; -import { DerivedFieldConfig } from '../types'; +import React, { useState } from 'react'; + import { ArrayVector, Field, FieldType, LinkModel } from '@grafana/data'; +import { LegacyForms } from '@grafana/ui'; + import { getFieldLinksForExplore } from '../../../../features/explore/utils/links'; +import { DerivedFieldConfig } from '../types'; + +const { FormField } = LegacyForms; type Props = { derivedFields?: DerivedFieldConfig[]; diff --git a/public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx b/public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx index cc65287af2b..8e63f6e7966 100644 --- a/public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { shallow } from 'enzyme'; -import { DerivedField } from './DerivedField'; -import { DataSourcePicker } from '@grafana/runtime'; +import React from 'react'; + import { DataSourceInstanceSettings } from '@grafana/data'; +import { DataSourcePicker } from '@grafana/runtime'; + +import { DerivedField } from './DerivedField'; jest.mock('app/features/plugins/datasource_srv', () => ({ getDatasourceSrv() { diff --git a/public/app/plugins/datasource/loki/configuration/DerivedField.tsx b/public/app/plugins/datasource/loki/configuration/DerivedField.tsx index b09642036f4..34997247f3d 100644 --- a/public/app/plugins/datasource/loki/configuration/DerivedField.tsx +++ b/public/app/plugins/datasource/loki/configuration/DerivedField.tsx @@ -1,10 +1,12 @@ -import React, { useEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { Button, DataLinkInput, stylesFactory, LegacyForms } from '@grafana/ui'; +import React, { useEffect, useState } from 'react'; +import { usePrevious } from 'react-use'; + import { VariableSuggestion } from '@grafana/data'; import { DataSourcePicker } from '@grafana/runtime'; +import { Button, DataLinkInput, stylesFactory, LegacyForms } from '@grafana/ui'; + import { DerivedFieldConfig } from '../types'; -import { usePrevious } from 'react-use'; const { Switch, FormField } = LegacyForms; diff --git a/public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx b/public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx index 1e0c3ac8482..aafaf7a2816 100644 --- a/public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/DerivedFields.test.tsx @@ -1,10 +1,12 @@ -import React from 'react'; import { mount } from 'enzyme'; -import { DerivedFields } from './DerivedFields'; -import { Button } from '@grafana/ui'; -import { DerivedField } from './DerivedField'; +import React from 'react'; import { act } from 'react-dom/test-utils'; +import { Button } from '@grafana/ui'; + +import { DerivedField } from './DerivedField'; +import { DerivedFields } from './DerivedFields'; + describe('DerivedFields', () => { let originalGetSelection: typeof window.getSelection; beforeAll(() => { diff --git a/public/app/plugins/datasource/loki/configuration/DerivedFields.tsx b/public/app/plugins/datasource/loki/configuration/DerivedFields.tsx index 4d2ffa00806..fd7ef7ff1b5 100644 --- a/public/app/plugins/datasource/loki/configuration/DerivedFields.tsx +++ b/public/app/plugins/datasource/loki/configuration/DerivedFields.tsx @@ -1,10 +1,13 @@ -import React, { useState } from 'react'; import { css } from '@emotion/css'; -import { Button, useTheme2 } from '@grafana/ui'; +import React, { useState } from 'react'; + import { GrafanaTheme2, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data'; +import { Button, useTheme2 } from '@grafana/ui'; + import { DerivedFieldConfig } from '../types'; -import { DerivedField } from './DerivedField'; + import { DebugSection } from './DebugSection'; +import { DerivedField } from './DerivedField'; const getStyles = (theme: GrafanaTheme2) => ({ infoText: css` diff --git a/public/app/plugins/datasource/loki/configuration/MaxLinesField.tsx b/public/app/plugins/datasource/loki/configuration/MaxLinesField.tsx index 5d3ddddf844..424499a6083 100644 --- a/public/app/plugins/datasource/loki/configuration/MaxLinesField.tsx +++ b/public/app/plugins/datasource/loki/configuration/MaxLinesField.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { LegacyForms } from '@grafana/ui'; const { FormField } = LegacyForms; diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index e7f84a0a51f..9bc7587cbfb 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,5 +1,8 @@ import { lastValueFrom, of, throwError } from 'rxjs'; import { take } from 'rxjs/operators'; +import { createFetchResponse } from 'test/helpers/createFetchResponse'; +import { getQueryOptions } from 'test/helpers/getQueryOptions'; + import { AbstractLabelOperator, AnnotationQueryRequest, @@ -13,16 +16,16 @@ import { toUtc, } from '@grafana/data'; import { BackendSrvRequest, FetchResponse } from '@grafana/runtime'; -import { isMetricsQuery, LokiDatasource, RangeQueryOptions } from './datasource'; -import { LokiQuery, LokiResponse, LokiResultType } from './types'; -import { getQueryOptions } from 'test/helpers/getQueryOptions'; -import { TemplateSrv } from 'app/features/templating/template_srv'; -import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { backendSrv } from 'app/core/services/backend_srv'; -import { CustomVariableModel } from '../../../features/variables/types'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { TemplateSrv } from 'app/features/templating/template_srv'; + import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; +import { CustomVariableModel } from '../../../features/variables/types'; + +import { isMetricsQuery, LokiDatasource, RangeQueryOptions } from './datasource'; import { makeMockLokiDatasource } from './mocks'; -import { createFetchResponse } from 'test/helpers/createFetchResponse'; +import { LokiQuery, LokiResponse, LokiResultType } from './types'; jest.mock('@grafana/runtime', () => ({ // @ts-ignore @@ -943,9 +946,9 @@ describe('LokiDatasource', () => { dataFrame: new MutableDataFrame({ fields: [ { - name: 'tsNs', - type: FieldType.string, - values: ['0'], + name: 'ts', + type: FieldType.time, + values: [0], }, ], }), @@ -957,8 +960,8 @@ describe('LokiDatasource', () => { jest.spyOn(ds.languageProvider, 'getLabelKeys').mockImplementation(() => ['bar']); const contextQuery = ds.prepareLogRowContextQueryTarget(row, 10, 'BACKWARD'); - expect(contextQuery.expr).toContain('baz'); - expect(contextQuery.expr).not.toContain('uniqueParsedLabel'); + expect(contextQuery.query.expr).toContain('baz'); + expect(contextQuery.query.expr).not.toContain('uniqueParsedLabel'); }); }); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 9de4a813dbc..79b808543aa 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -1,8 +1,8 @@ // Libraries import { cloneDeep, isEmpty, map as lodashMap } from 'lodash'; +import Prism from 'prismjs'; import { lastValueFrom, merge, Observable, of, throwError } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; -import Prism from 'prismjs'; // Types import { @@ -33,38 +33,37 @@ import { ScopedVars, TimeRange, rangeUtil, + toUtc, } from '@grafana/data'; import { BackendSrvRequest, FetchError, getBackendSrv, config, DataSourceWithBackend } from '@grafana/runtime'; -import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; -import { addLabelToQuery } from './add_label_to_query'; -import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; +import { queryLogsVolume } from 'app/core/logs_model'; import { convertToWebSocketUrl } from 'app/core/utils/explore'; -import { - lokiResultsToTableModel, - lokiStreamsToDataFrames, - lokiStreamsToRawDataFrame, - processRangeQueryResponse, -} from './result_transformer'; -import { transformBackendResult } from './backendResultTransformer'; -import { addParsedLabelToQuery, getNormalizedLokiQuery, queryHasPipeParser } from './query_utils'; +import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; +import { serializeParams } from '../../../core/utils/fetch'; +import { renderLegendFormat } from '../prometheus/legend'; + +import { addLabelToQuery } from './add_label_to_query'; +import { transformBackendResult } from './backendResultTransformer'; +import { DEFAULT_RESOLUTION } from './components/LokiOptionFields'; +import LanguageProvider from './language_provider'; +import { LiveStreams, LokiLiveTarget } from './live_streams'; +import { addParsedLabelToQuery, getNormalizedLokiQuery, queryHasPipeParser } from './query_utils'; +import { lokiResultsToTableModel, lokiStreamsToDataFrames, processRangeQueryResponse } from './result_transformer'; +import { sortDataFrameByTime } from './sortDataFrame'; +import { doLokiChannelStream } from './streaming'; +import syntax from './syntax'; import { LokiOptions, LokiQuery, + LokiQueryDirection, LokiQueryType, LokiRangeQueryRequest, LokiResultType, LokiStreamResponse, } from './types'; -import { LiveStreams, LokiLiveTarget } from './live_streams'; -import LanguageProvider from './language_provider'; -import { serializeParams } from '../../../core/utils/fetch'; -import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider'; -import syntax from './syntax'; -import { DEFAULT_RESOLUTION } from './components/LokiOptionFields'; -import { queryLogsVolume } from 'app/core/logs_model'; -import { doLokiChannelStream } from './streaming'; -import { renderLegendFormat } from '../prometheus/legend'; export type RangeQueryOptions = DataQueryRequest | AnnotationQueryRequest; export const DEFAULT_MAX_LINES = 1000; @@ -75,7 +74,6 @@ const RANGE_QUERY_ENDPOINT = `${LOKI_ENDPOINT}/query_range`; const INSTANT_QUERY_ENDPOINT = `${LOKI_ENDPOINT}/query`; const DEFAULT_QUERY_PARAMS: Partial = { - direction: 'BACKWARD', limit: DEFAULT_MAX_LINES, query: '', }; @@ -252,6 +250,7 @@ export class LokiDatasource query: target.expr, time: `${timeNs + (1e9 - (timeNs % 1e9))}`, limit: Math.min(queryLimit || Infinity, this.maxLines), + direction: target.direction === LokiQueryDirection.Forward ? 'FORWARD' : 'BACKWARD', }; /** Used only for results of metrics instant queries */ @@ -311,6 +310,7 @@ export class LokiDatasource ...range, query, limit, + direction: target.direction === LokiQueryDirection.Forward ? 'FORWARD' : 'BACKWARD', }; } @@ -439,8 +439,13 @@ export class LokiDatasource } async metadataRequest(url: string, params?: Record) { - const res = await lastValueFrom(this._request(url, params, { hideFromInspector: true })); - return res.data.data || res.data.values || []; + if (config.featureToggles.lokiBackendMode) { + const res = await this.getResource(url, params); + return res.data || res.values || []; + } else { + const res = await lastValueFrom(this._request(url, params, { hideFromInspector: true })); + return res.data.data || res.data.values || []; + } } async metricFindQuery(query: string) { @@ -554,15 +559,58 @@ export class LokiDatasource } getLogRowContext = (row: LogRowModel, options?: RowContextOptions): Promise<{ data: DataFrame[] }> => { - const target = this.prepareLogRowContextQueryTarget( - row, - (options && options.limit) || 10, - (options && options.direction) || 'BACKWARD' - ); + const direction = (options && options.direction) || 'BACKWARD'; + const limit = (options && options.limit) || 10; + const { query, range } = this.prepareLogRowContextQueryTarget(row, limit, direction); + + const processDataFrame = (frame: DataFrame): DataFrame => { + // log-row-context requires specific field-names to work, so we set them here: "ts", "line", "id" + const cache = new FieldCache(frame); + const timestampField = cache.getFirstFieldOfType(FieldType.time); + const lineField = cache.getFirstFieldOfType(FieldType.string); + const idField = cache.getFieldByName('id'); + + if (timestampField === undefined || lineField === undefined || idField === undefined) { + // this should never really happen, but i want to keep typescript happy + return { ...frame, fields: [] }; + } + + return { + ...frame, + fields: [ + { + ...timestampField, + name: 'ts', + }, + { + ...lineField, + name: 'line', + }, + { + ...idField, + name: 'id', + }, + ], + }; + }; + + const processResults = (result: DataQueryResponse): DataQueryResponse => { + const frames: DataFrame[] = result.data; + const processedFrames = frames + .map((frame) => sortDataFrameByTime(frame, 'DESCENDING')) + .map((frame) => processDataFrame(frame)); // rename fields if needed + + return { + ...result, + data: processedFrames, + }; + }; + + // this can only be called from explore currently + const app = CoreApp.Explore; - const reverse = options && options.direction === 'FORWARD'; return lastValueFrom( - this._request(RANGE_QUERY_ENDPOINT, target).pipe( + this.query(makeRequest(query, range, app, `log-row-context-query-${direction}`)).pipe( catchError((err) => { const error: DataQueryError = { message: 'Error during context query. Please check JS console logs.', @@ -571,18 +619,18 @@ export class LokiDatasource }; throw error; }), - switchMap((res) => - of({ - data: res.data ? [lokiStreamsToRawDataFrame(res.data.data.result, reverse)] : [], - }) - ) + switchMap((res) => of(processResults(res))) ) ); }; - prepareLogRowContextQueryTarget = (row: LogRowModel, limit: number, direction: 'BACKWARD' | 'FORWARD') => { + prepareLogRowContextQueryTarget = ( + row: LogRowModel, + limit: number, + direction: 'BACKWARD' | 'FORWARD' + ): { query: LokiQuery; range: TimeRange } => { const labels = this.languageProvider.getLabelKeys(); - const query = Object.keys(row.labels) + const expr = Object.keys(row.labels) .map((label: string) => { if (labels.includes(label)) { // escape backslashes in label as users can't escape them by themselves @@ -595,36 +643,49 @@ export class LokiDatasource .join(','); const contextTimeBuffer = 2 * 60 * 60 * 1000; // 2h buffer - const commonTargetOptions = { - limit, - query: `{${query}}`, - expr: `{${query}}`, - direction, + + const queryDirection = direction === 'FORWARD' ? LokiQueryDirection.Forward : LokiQueryDirection.Backward; + + const query: LokiQuery = { + expr: `{${expr}}`, + queryType: LokiQueryType.Range, + refId: '', + maxLines: limit, + direction: queryDirection, }; const fieldCache = new FieldCache(row.dataFrame); - const nsField = fieldCache.getFieldByName('tsNs')!; - const nsTimestamp = nsField.values.get(row.rowIndex); - - if (direction === 'BACKWARD') { - return { - ...commonTargetOptions, - // convert to ns, we loose some precision here but it is not that important at the far points of the context - start: row.timeEpochMs - contextTimeBuffer + '000000', - end: nsTimestamp, - direction, - }; - } else { - return { - ...commonTargetOptions, - // start param in Loki API is inclusive so we'll have to filter out the row that this request is based from - // and any other that were logged in the same ns but before the row. Right now these rows will be lost - // because the are before but came it he response that should return only rows after. - start: nsTimestamp, - // convert to ns, we loose some precision here but it is not that important at the far points of the context - end: row.timeEpochMs + contextTimeBuffer + '000000', - }; + const tsField = fieldCache.getFirstFieldOfType(FieldType.time); + if (tsField === undefined) { + throw new Error('loki: dataframe missing time-field, should never happen'); } + const tsValue = tsField.values.get(row.rowIndex); + const timestamp = toUtc(tsValue); + + const range = + queryDirection === LokiQueryDirection.Forward + ? { + // start param in Loki API is inclusive so we'll have to filter out the row that this request is based from + // and any other that were logged in the same ns but before the row. Right now these rows will be lost + // because the are before but came it he response that should return only rows after. + from: timestamp, + // convert to ns, we loose some precision here but it is not that important at the far points of the context + to: toUtc(row.timeEpochMs + contextTimeBuffer), + } + : { + // convert to ns, we loose some precision here but it is not that important at the far points of the context + from: toUtc(row.timeEpochMs - contextTimeBuffer), + to: timestamp, + }; + + return { + query, + range: { + from: range.from, + to: range.to, + raw: range, + }, + }; }; testDatasource() { diff --git a/public/app/plugins/datasource/loki/language_provider.test.ts b/public/app/plugins/datasource/loki/language_provider.test.ts index 70ad8daeb10..168d696855e 100644 --- a/public/app/plugins/datasource/loki/language_provider.test.ts +++ b/public/app/plugins/datasource/loki/language_provider.test.ts @@ -1,11 +1,11 @@ import Plain from 'slate-plain-serializer'; -import LanguageProvider, { LokiHistoryItem } from './language_provider'; +import { AbstractLabelOperator } from '@grafana/data'; import { TypeaheadInput } from '@grafana/ui'; -import { makeMockLokiDatasource } from './mocks'; import { LokiDatasource } from './datasource'; -import { AbstractLabelOperator } from '@grafana/data'; +import LanguageProvider, { LokiHistoryItem } from './language_provider'; +import { makeMockLokiDatasource } from './mocks'; import { LokiQueryType } from './types'; jest.mock('app/store/store', () => ({ diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index 1fd83b96c9d..330df0562a2 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -1,23 +1,19 @@ -// Libraries import { chain, difference } from 'lodash'; import LRU from 'lru-cache'; +import Prism, { Grammar } from 'prismjs'; -// Services & Utils +import { dateTime, AbsoluteTimeRange, LanguageProvider, HistoryItem, AbstractQuery } from '@grafana/data'; +import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui'; import { extractLabelMatchers, parseSelector, processLabels, toPromLikeExpr, } from 'app/plugins/datasource/prometheus/language_utils'; -import syntax, { FUNCTIONS, PIPE_PARSERS, PIPE_OPERATORS } from './syntax'; - -// Types -import { LokiQuery, LokiQueryType } from './types'; -import { dateTime, AbsoluteTimeRange, LanguageProvider, HistoryItem, AbstractQuery } from '@grafana/data'; import { LokiDatasource } from './datasource'; -import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui'; -import Prism, { Grammar } from 'prismjs'; +import syntax, { FUNCTIONS, PIPE_PARSERS, PIPE_OPERATORS } from './syntax'; +import { LokiQuery, LokiQueryType } from './types'; const DEFAULT_KEYS = ['job', 'namespace']; const EMPTY_SELECTOR = '{}'; diff --git a/public/app/plugins/datasource/loki/live_streams.test.ts b/public/app/plugins/datasource/loki/live_streams.test.ts index 810b229f3dd..8919e5d71fa 100644 --- a/public/app/plugins/datasource/loki/live_streams.test.ts +++ b/public/app/plugins/datasource/loki/live_streams.test.ts @@ -1,9 +1,11 @@ +import { noop } from 'lodash'; import { Observable, Subject, of, throwError, concat } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import * as rxJsWebSocket from 'rxjs/webSocket'; -import { LiveStreams } from './live_streams'; + import { DataFrame, DataFrameView, formatLabels, Labels } from '@grafana/data'; -import { noop } from 'lodash'; + +import { LiveStreams } from './live_streams'; import { LokiTailResponse } from './types'; let fakeSocket: Subject; diff --git a/public/app/plugins/datasource/loki/live_streams.ts b/public/app/plugins/datasource/loki/live_streams.ts index a2a68ec9fd6..82319bec041 100644 --- a/public/app/plugins/datasource/loki/live_streams.ts +++ b/public/app/plugins/datasource/loki/live_streams.ts @@ -1,9 +1,11 @@ -import { DataFrame, FieldType, parseLabels, KeyValue, CircularDataFrame } from '@grafana/data'; import { Observable, throwError, timer } from 'rxjs'; -import { webSocket } from 'rxjs/webSocket'; -import { LokiTailResponse } from './types'; import { finalize, map, retryWhen, mergeMap } from 'rxjs/operators'; +import { webSocket } from 'rxjs/webSocket'; + +import { DataFrame, FieldType, parseLabels, KeyValue, CircularDataFrame } from '@grafana/data'; + import { appendResponseToBufferedData } from './result_transformer'; +import { LokiTailResponse } from './types'; /** * Maps directly to a query in the UI (refId is key) diff --git a/public/app/plugins/datasource/loki/makeTableFrames.test.ts b/public/app/plugins/datasource/loki/makeTableFrames.test.ts index 2f33edf8792..2fbb072e24b 100644 --- a/public/app/plugins/datasource/loki/makeTableFrames.test.ts +++ b/public/app/plugins/datasource/loki/makeTableFrames.test.ts @@ -1,4 +1,5 @@ import { ArrayVector, DataFrame, FieldType } from '@grafana/data'; + import { makeTableFrames } from './makeTableFrames'; const frame1: DataFrame = { diff --git a/public/app/plugins/datasource/loki/makeTableFrames.ts b/public/app/plugins/datasource/loki/makeTableFrames.ts index ac0fd971cfa..5baf3721b41 100644 --- a/public/app/plugins/datasource/loki/makeTableFrames.ts +++ b/public/app/plugins/datasource/loki/makeTableFrames.ts @@ -1,6 +1,7 @@ -import { DataFrame, Field, FieldType, ArrayVector } from '@grafana/data'; import { groupBy } from 'lodash'; +import { DataFrame, Field, FieldType, ArrayVector } from '@grafana/data'; + export function makeTableFrames(instantMetricFrames: DataFrame[]): DataFrame[] { // first we remove frames that have no refId // (we will group them by refId, so we need it to be set) diff --git a/public/app/plugins/datasource/loki/mocks.ts b/public/app/plugins/datasource/loki/mocks.ts index 35d37f6e0ef..9d5e7b0ca44 100644 --- a/public/app/plugins/datasource/loki/mocks.ts +++ b/public/app/plugins/datasource/loki/mocks.ts @@ -1,8 +1,10 @@ -import { LokiDatasource, LOKI_ENDPOINT } from './datasource'; import { DataSourceSettings } from '@grafana/data'; -import { LokiOptions } from './types'; + import { createDatasourceSettings } from '../../../features/datasources/mocks'; +import { LokiDatasource, LOKI_ENDPOINT } from './datasource'; +import { LokiOptions } from './types'; + interface Labels { [label: string]: string[]; } diff --git a/public/app/plugins/datasource/loki/module.ts b/public/app/plugins/datasource/loki/module.ts index fe7b4beb67d..6880f9da640 100644 --- a/public/app/plugins/datasource/loki/module.ts +++ b/public/app/plugins/datasource/loki/module.ts @@ -1,10 +1,10 @@ import { DataSourcePlugin } from '@grafana/data'; -import { LokiDatasource } from './datasource'; +import { LokiAnnotationsQueryCtrl } from './LokiAnnotationsQueryCtrl'; import LokiCheatSheet from './components/LokiCheatSheet'; import LokiQueryEditorByApp from './components/LokiQueryEditorByApp'; -import { LokiAnnotationsQueryCtrl } from './LokiAnnotationsQueryCtrl'; import { ConfigEditor } from './configuration/ConfigEditor'; +import { LokiDatasource } from './datasource'; export const plugin = new DataSourcePlugin(LokiDatasource) .setQueryEditor(LokiQueryEditorByApp) diff --git a/public/app/plugins/datasource/loki/query_utils.test.ts b/public/app/plugins/datasource/loki/query_utils.test.ts index 4e087955488..43cb219f30d 100644 --- a/public/app/plugins/datasource/loki/query_utils.test.ts +++ b/public/app/plugins/datasource/loki/query_utils.test.ts @@ -1,5 +1,5 @@ -import { LokiQuery, LokiQueryType } from './types'; import { getHighlighterExpressionsFromQuery, getNormalizedLokiQuery } from './query_utils'; +import { LokiQuery, LokiQueryType } from './types'; describe('getHighlighterExpressionsFromQuery', () => { it('returns no expressions for empty query', () => { diff --git a/public/app/plugins/datasource/loki/query_utils.ts b/public/app/plugins/datasource/loki/query_utils.ts index f4432345045..1f9ede7022f 100644 --- a/public/app/plugins/datasource/loki/query_utils.ts +++ b/public/app/plugins/datasource/loki/query_utils.ts @@ -1,4 +1,5 @@ import { escapeRegExp } from 'lodash'; + import { PIPE_PARSERS } from './syntax'; import { LokiQuery, LokiQueryType } from './types'; diff --git a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts index 1139e312a9a..34f76b74a77 100644 --- a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts +++ b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts @@ -1,5 +1,6 @@ import { LokiAndPromQueryModellerBase } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase'; import { QueryBuilderLabelFilter } from '../../prometheus/querybuilder/shared/types'; + import { getOperationDefinitions } from './operations'; import { LokiOperationId, LokiQueryPattern, LokiVisualQueryOperationCategory } from './types'; diff --git a/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts b/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts index cb2b7f1ea9a..a5759ff3060 100644 --- a/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts +++ b/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts @@ -4,6 +4,7 @@ import { QueryBuilderOperationDef, QueryBuilderOperationParamDef, } from '../../prometheus/querybuilder/shared/types'; + import { LokiOperationId, LokiVisualQueryOperationCategory } from './types'; export const binaryScalarDefs = [ diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx index f931249082a..293d3852e73 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx @@ -1,11 +1,14 @@ -import React from 'react'; import { render, screen, getAllByRole, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { LokiQueryBuilder } from './LokiQueryBuilder'; -import { LokiDatasource } from '../../datasource'; -import { LokiVisualQuery } from '../types'; +import React from 'react'; + import { PanelData } from '@grafana/data'; +import { LokiDatasource } from '../../datasource'; +import { LokiOperationId, LokiVisualQuery } from '../types'; + +import { LokiQueryBuilder } from './LokiQueryBuilder'; + const defaultQuery: LokiVisualQuery = { labels: [{ op: '=', label: 'baz', value: 'bar' }], operations: [], @@ -15,12 +18,22 @@ describe('LokiQueryBuilder', () => { it('tries to load labels when no labels are selected', async () => { const { datasource } = setup(); datasource.languageProvider.fetchSeriesLabels = jest.fn().mockReturnValue({ job: ['a'], instance: ['b'] }); - userEvent.click(screen.getByLabelText('Add')); + await userEvent.click(screen.getByLabelText('Add')); const labels = screen.getByText(/Labels/); - const selects = getAllByRole(labels.parentElement!, 'combobox'); - userEvent.click(selects[3]); + const selects = getAllByRole(labels.parentElement!.parentElement!.parentElement!, 'combobox'); + await userEvent.click(selects[3]); await waitFor(() => expect(screen.getByText('job')).toBeInTheDocument()); }); + + it('shows error for query with operations and no stream selector', async () => { + setup({ labels: [], operations: [{ id: LokiOperationId.Logfmt, params: [] }] }); + expect(screen.getByText('You need to specify at least 1 label filter (stream selector)')).toBeInTheDocument(); + }); + + it('shows no error for query with empty __line_contains operation and no stream selector', async () => { + setup({ labels: [], operations: [{ id: LokiOperationId.LineContains, params: [''] }] }); + expect(screen.queryByText('You need to specify at least 1 label filter (stream selector)')).not.toBeInTheDocument(); + }); }); function setup(query: LokiVisualQuery = defaultQuery, data?: PanelData) { diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx index 9b84ad88348..4ea05365853 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { LokiVisualQuery } from '../types'; -import { LokiDatasource } from '../../datasource'; -import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters'; -import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList'; -import { QueryBuilderLabelFilter } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; -import { lokiQueryModeller } from '../LokiQueryModeller'; +import React, { useMemo } from 'react'; + import { DataSourceApi, SelectableValue } from '@grafana/data'; import { EditorRow } from '@grafana/experimental'; +import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters'; +import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList'; import { OperationsEditorRow } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationsEditorRow'; +import { QueryBuilderLabelFilter } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; + +import { LokiDatasource } from '../../datasource'; +import { lokiQueryModeller } from '../LokiQueryModeller'; +import { LokiOperationId, LokiVisualQuery } from '../types'; + import { NestedQueryList } from './NestedQueryList'; export interface Props { @@ -57,6 +60,18 @@ export const LokiQueryBuilder = React.memo(({ datasource, query, nested, return result[forLabelInterpolated] ?? []; }; + const labelFilterError: string | undefined = useMemo(() => { + const { labels, operations: op } = query; + if (!labels.length && op.length) { + // We don't want to show error for initial state with empty line contains operation + if (op.length === 1 && op[0].id === LokiOperationId.LineContains && op[0].params[0] === '') { + return undefined; + } + return 'You need to specify at least 1 label filter (stream selector)'; + } + return undefined; + }, [query]); + return ( <> @@ -69,6 +84,7 @@ export const LokiQueryBuilder = React.memo(({ datasource, query, nested, } labelsFilters={query.labels} onChange={onChangeLabels} + error={labelFilterError} /> diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx index 80717a06fea..170939451b6 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx @@ -1,9 +1,12 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer'; -import { LokiDatasource } from '../../datasource'; +import React from 'react'; + import { addOperation } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList.testUtils'; +import { LokiDatasource } from '../../datasource'; + +import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer'; + describe('LokiQueryBuilderContainer', () => { it('translates query between string and model', async () => { const props = { @@ -30,7 +33,7 @@ describe('LokiQueryBuilderContainer', () => { }; render(); expect(screen.getByText('testjob')).toBeInTheDocument(); - addOperation('Range functions', 'Rate'); + await addOperation('Range functions', 'Rate'); expect(props.onChange).toBeCalledWith({ expr: 'rate({job="testjob"} [$__interval])', refId: 'A', diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx index 32390a99fa3..3e20f62bcf3 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx @@ -1,12 +1,14 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import React, { useEffect, useReducer } from 'react'; + import { LokiDatasource } from '../../datasource'; import { LokiQuery } from '../../types'; -import { buildVisualQueryFromString } from '../parsing'; import { lokiQueryModeller } from '../LokiQueryModeller'; +import { buildVisualQueryFromString } from '../parsing'; +import { LokiVisualQuery } from '../types'; + import { LokiQueryBuilder } from './LokiQueryBuilder'; import { QueryPreview } from './QueryPreview'; -import { LokiVisualQuery } from '../types'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; export interface Props { query: LokiQuery; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderExplained.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderExplained.tsx index 9b53334a453..257ff64722c 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderExplained.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderExplained.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import { LokiVisualQuery } from '../types'; + import { Stack } from '@grafana/experimental'; -import { lokiQueryModeller } from '../LokiQueryModeller'; -import { OperationListExplained } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained'; import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox'; +import { OperationListExplained } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained'; + +import { lokiQueryModeller } from '../LokiQueryModeller'; import { buildVisualQueryFromString } from '../parsing'; +import { LokiVisualQuery } from '../types'; export interface Props { query: string; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index ae015f9d3fd..511bc2d7374 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -1,8 +1,10 @@ -import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { LokiQuery, LokiQueryType } from '../../types'; -import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { LokiQuery, LokiQueryType } from '../../types'; + +import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions'; describe('LokiQueryBuilderOptions', () => { it('Can change query type', async () => { @@ -25,7 +27,7 @@ describe('LokiQueryBuilderOptions', () => { screen.getByTitle('Click to edit options').click(); const element = screen.getByLabelText('Legend'); - userEvent.type(element, 'asd'); + await userEvent.type(element, 'asd'); fireEvent.keyDown(element, { key: 'Enter', code: 'Enter', charCode: 13 }); expect(props.onChange).toHaveBeenCalledWith({ diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 0aac629b32e..209c2e725fe 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import { EditorRow, EditorField } from '@grafana/experimental'; + import { SelectableValue } from '@grafana/data'; +import { EditorRow, EditorField } from '@grafana/experimental'; import { RadioButtonGroup, Select } from '@grafana/ui'; -import { LokiQuery, LokiQueryType } from '../../types'; -import { QueryOptionGroup } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup'; -import { preprocessMaxLines, queryTypeOptions, RESOLUTION_OPTIONS } from '../../components/LokiOptionFields'; import { AutoSizeInput } from 'app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput'; +import { QueryOptionGroup } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup'; + +import { preprocessMaxLines, queryTypeOptions, RESOLUTION_OPTIONS } from '../../components/LokiOptionFields'; import { isMetricsQuery } from '../../datasource'; +import { LokiQuery, LokiQueryType } from '../../types'; export interface Props { query: LokiQuery; @@ -58,12 +60,7 @@ export const LokiQueryBuilderOptions = React.memo(({ query, onChange, onR /> - + {showMaxLines && ( diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx index 41e6bc2937e..1354458a7ac 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx @@ -1,10 +1,12 @@ -import React from 'react'; -import { testIds } from '../../components/LokiQueryEditor'; -import { useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; +import React from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; -import { LokiQueryEditorProps } from '../../components/types'; +import { useStyles2 } from '@grafana/ui'; + +import { testIds } from '../../components/LokiQueryEditor'; import { LokiQueryField } from '../../components/LokiQueryField'; +import { LokiQueryEditorProps } from '../../components/types'; export function LokiQueryCodeEditor({ query, datasource, range, onRunQuery, onChange, data }: LokiQueryEditorProps) { const styles = useStyles2(getStyles); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx index 23c9dae50fb..1b483c53dd1 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx @@ -1,12 +1,15 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { LokiDatasource } from '../../datasource'; import { cloneDeep, defaultsDeep } from 'lodash'; -import { LokiQuery, LokiQueryType } from '../../types'; -import { LokiQueryEditorSelector } from './LokiQueryEditorSelector'; +import React from 'react'; + import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; +import { LokiDatasource } from '../../datasource'; +import { LokiQuery, LokiQueryType } from '../../types'; + +import { LokiQueryEditorSelector } from './LokiQueryEditorSelector'; + const defaultQuery = { refId: 'A', expr: '{label1="foo", label2="bar"}', @@ -73,7 +76,7 @@ describe('LokiQueryEditorSelector', () => { it('changes to builder mode', async () => { const { onChange } = renderWithMode(QueryEditorMode.Code); - switchToMode(QueryEditorMode.Builder); + await switchToMode(QueryEditorMode.Builder); expect(onChange).toBeCalledWith({ refId: 'A', expr: defaultQuery.expr, @@ -108,7 +111,7 @@ describe('LokiQueryEditorSelector', () => { it('changes to code mode', async () => { const { onChange } = renderWithMode(QueryEditorMode.Builder); - switchToMode(QueryEditorMode.Code); + await switchToMode(QueryEditorMode.Code); expect(onChange).toBeCalledWith({ refId: 'A', expr: defaultQuery.expr, @@ -119,7 +122,7 @@ describe('LokiQueryEditorSelector', () => { it('changes to explain mode', async () => { const { onChange } = renderWithMode(QueryEditorMode.Code); - switchToMode(QueryEditorMode.Explain); + await switchToMode(QueryEditorMode.Explain); expect(onChange).toBeCalledWith({ refId: 'A', expr: defaultQuery.expr, @@ -134,7 +137,7 @@ describe('LokiQueryEditorSelector', () => { expr: 'rate({instance="host.docker.internal:3000"}[$__interval])', editorMode: QueryEditorMode.Code, }); - switchToMode(QueryEditorMode.Builder); + await switchToMode(QueryEditorMode.Builder); rerender( ((props) => { const { onChange, onRunQuery, data } = props; @@ -68,21 +71,25 @@ export const LokiQueryEditorSelector = React.memo((props) onDismiss={() => setParseModalOpen(false)} /> - { - const result = buildVisualQueryFromString(query.expr || ''); - result.query.operations = value?.operations!; - onChange({ - ...query, - expr: lokiQueryModeller.renderQuery(result.query), - }); - }} - options={lokiQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))} - /> - + {editorMode === QueryEditorMode.Builder && ( + <> + { + const result = buildVisualQueryFromString(query.expr || ''); + result.query.operations = value?.operations!; + onChange({ + ...query, + expr: lokiQueryModeller.renderQuery(result.query), + }); + }} + options={lokiQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))} + /> + + + )} - ))} - + + +
<> {places.map((p) => { - const v = placement[p]; + const v = placement![p]; if (v == null) { return null; } return ( - console.log('TODO, edit!!!', p, v)} /> + onPositionChange(v, p)} /> ); diff --git a/public/app/plugins/panel/canvas/editor/elementEditor.tsx b/public/app/plugins/panel/canvas/editor/elementEditor.tsx index 6a5ce60757a..e0b75016b7b 100644 --- a/public/app/plugins/panel/canvas/editor/elementEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/elementEditor.tsx @@ -1,11 +1,13 @@ import { get as lodashGet } from 'lodash'; -import { optionBuilder } from './options'; -import { CanvasElementOptions, canvasElementRegistry, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas'; + import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; -import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; +import { CanvasElementOptions, canvasElementRegistry, DEFAULT_CANVAS_ELEMENT_CONFIG } from 'app/features/canvas'; import { ElementState } from 'app/features/canvas/runtime/element'; import { Scene } from 'app/features/canvas/runtime/scene'; +import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; + import { PlacementEditor } from './PlacementEditor'; +import { optionBuilder } from './options'; export interface CanvasEditorOptions { element: ElementState; @@ -84,7 +86,7 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions< category: ['Layout'], id: 'content', path: '__', // not used - name: 'Anchor', + name: 'Constraints', editor: PlacementEditor, settings: opts, }); diff --git a/public/app/plugins/panel/canvas/editor/layerEditor.tsx b/public/app/plugins/panel/canvas/editor/layerEditor.tsx index ded0763413d..9c07fd425c1 100644 --- a/public/app/plugins/panel/canvas/editor/layerEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/layerEditor.tsx @@ -1,13 +1,16 @@ import { get as lodashGet } from 'lodash'; -import { optionBuilder } from './options'; + import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; -import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; -import { InstanceState } from '../CanvasPanel'; -import { LayerElementListEditor } from './LayerElementListEditor'; +import { ElementState } from 'app/features/canvas/runtime/element'; import { GroupState } from 'app/features/canvas/runtime/group'; import { Scene } from 'app/features/canvas/runtime/scene'; -import { ElementState } from 'app/features/canvas/runtime/element'; +import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; + +import { InstanceState } from '../CanvasPanel'; + +import { LayerElementListEditor } from './LayerElementListEditor'; import { PlacementEditor } from './PlacementEditor'; +import { optionBuilder } from './options'; export interface LayerEditorProps { scene: Scene; @@ -81,7 +84,7 @@ export function getLayerEditor(opts: InstanceState): NestedPanelOptions(CanvasPanel) .setNoPadding() // extend to panel edges diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx index b31f6eaa1c1..54c797899be 100644 --- a/public/app/plugins/panel/dashlist/DashList.tsx +++ b/public/app/plugins/panel/dashlist/DashList.tsx @@ -1,18 +1,19 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { take } from 'lodash'; import { css, cx } from '@emotion/css'; +import { take } from 'lodash'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { GrafanaTheme2, InterpolateFunction, PanelProps } from '@grafana/data'; import { CustomScrollbar, stylesFactory, useStyles2 } from '@grafana/ui'; import { Icon, IconProps } from '@grafana/ui/src/components/Icon/Icon'; import { getFocusStyles } from '@grafana/ui/src/themes/mixins'; import { getBackendSrv } from 'app/core/services/backend_srv'; -import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import impressionSrv from 'app/core/services/impression_srv'; -import { DashboardSearchHit } from 'app/features/search/types'; -import { getStyles } from './styles'; -import { PanelLayout, PanelOptions } from './models.gen'; +import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { SearchCard } from 'app/features/search/components/SearchCard'; +import { DashboardSearchHit } from 'app/features/search/types'; + +import { PanelLayout, PanelOptions } from './models.gen'; +import { getStyles } from './styles'; type Dashboard = DashboardSearchHit & { isSearchResult?: boolean; isRecent?: boolean }; diff --git a/public/app/plugins/panel/dashlist/module.tsx b/public/app/plugins/panel/dashlist/module.tsx index 742e48d2eb0..d0fa3e8c036 100644 --- a/public/app/plugins/panel/dashlist/module.tsx +++ b/public/app/plugins/panel/dashlist/module.tsx @@ -1,13 +1,16 @@ -import { PanelModel, PanelPlugin } from '@grafana/data'; -import { DashList } from './DashList'; import React from 'react'; + +import { PanelModel, PanelPlugin } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { TagsInput } from '@grafana/ui'; + import { ALL_FOLDER, GENERAL_FOLDER, ReadonlyFolderPicker, } from '../../../core/components/Select/ReadonlyFolderPicker/ReadonlyFolderPicker'; -import { config } from '@grafana/runtime'; + +import { DashList } from './DashList'; import { defaultPanelOptions, PanelLayout, PanelOptions } from './models.gen'; export const plugin = new PanelPlugin(DashList) diff --git a/public/app/plugins/panel/debug/CursorView.tsx b/public/app/plugins/panel/debug/CursorView.tsx index f075666f334..124ea8482e2 100644 --- a/public/app/plugins/panel/debug/CursorView.tsx +++ b/public/app/plugins/panel/debug/CursorView.tsx @@ -1,4 +1,6 @@ import React, { Component } from 'react'; +import { Subscription } from 'rxjs'; + import { EventBus, LegacyGraphHoverEvent, @@ -7,8 +9,8 @@ import { DataHoverClearEvent, BusEventBase, } from '@grafana/data'; -import { Subscription } from 'rxjs'; import { CustomScrollbar } from '@grafana/ui'; + import { DataHoverView } from '../geomap/components/DataHoverView'; interface Props { diff --git a/public/app/plugins/panel/debug/DebugPanel.tsx b/public/app/plugins/panel/debug/DebugPanel.tsx index dc964027353..c3c9f007a63 100644 --- a/public/app/plugins/panel/debug/DebugPanel.tsx +++ b/public/app/plugins/panel/debug/DebugPanel.tsx @@ -1,11 +1,12 @@ import React, { Component } from 'react'; + import { PanelProps } from '@grafana/data'; -import { DebugPanelOptions, DebugMode } from './types'; +import { CursorView } from './CursorView'; import { EventBusLoggerPanel } from './EventBusLogger'; import { RenderInfoViewer } from './RenderInfoViewer'; -import { CursorView } from './CursorView'; import { StateView } from './StateView'; +import { DebugPanelOptions, DebugMode } from './types'; type Props = PanelProps; diff --git a/public/app/plugins/panel/debug/EventBusLogger.tsx b/public/app/plugins/panel/debug/EventBusLogger.tsx index b38824a8cfb..c13501abd61 100644 --- a/public/app/plugins/panel/debug/EventBusLogger.tsx +++ b/public/app/plugins/panel/debug/EventBusLogger.tsx @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; -import { CustomScrollbar } from '@grafana/ui'; +import { PartialObserver, Unsubscribable } from 'rxjs'; + import { BusEvent, CircularVector, @@ -8,7 +9,7 @@ import { DataSelectEvent, EventBus, } from '@grafana/data'; -import { PartialObserver, Unsubscribable } from 'rxjs'; +import { CustomScrollbar } from '@grafana/ui'; interface Props { eventBus: EventBus; diff --git a/public/app/plugins/panel/debug/RenderInfoViewer.tsx b/public/app/plugins/panel/debug/RenderInfoViewer.tsx index d090b04d849..3acc8a7798f 100644 --- a/public/app/plugins/panel/debug/RenderInfoViewer.tsx +++ b/public/app/plugins/panel/debug/RenderInfoViewer.tsx @@ -1,4 +1,5 @@ import React, { Component } from 'react'; + import { compareArrayValues, compareDataFrameStructures, @@ -8,9 +9,9 @@ import { PanelProps, ReducerID, } from '@grafana/data'; +import { IconButton } from '@grafana/ui'; import { DebugPanelOptions, UpdateCounters, UpdateConfig } from './types'; -import { IconButton } from '@grafana/ui'; type Props = PanelProps; diff --git a/public/app/plugins/panel/debug/StateView.tsx b/public/app/plugins/panel/debug/StateView.tsx index 76746e25a08..ffee7405a55 100644 --- a/public/app/plugins/panel/debug/StateView.tsx +++ b/public/app/plugins/panel/debug/StateView.tsx @@ -1,6 +1,8 @@ import React, { FormEvent } from 'react'; + import { PanelOptionsEditorProps, PanelProps } from '@grafana/data'; import { Field, Input, usePanelContext } from '@grafana/ui'; + import { DebugPanelOptions } from './types'; export function StateView(props: PanelProps) { diff --git a/public/app/plugins/panel/debug/module.tsx b/public/app/plugins/panel/debug/module.tsx index a2bfecebece..dea12e74bde 100644 --- a/public/app/plugins/panel/debug/module.tsx +++ b/public/app/plugins/panel/debug/module.tsx @@ -1,4 +1,5 @@ import { PanelPlugin } from '@grafana/data'; + import { DebugPanel } from './DebugPanel'; import { StateViewEditor } from './StateView'; import { DebugMode, DebugPanelOptions } from './types'; diff --git a/public/app/plugins/panel/gauge/GaugeMigrations.test.ts b/public/app/plugins/panel/gauge/GaugeMigrations.test.ts index 1f63ae5641b..bc2868c2474 100644 --- a/public/app/plugins/panel/gauge/GaugeMigrations.test.ts +++ b/public/app/plugins/panel/gauge/GaugeMigrations.test.ts @@ -1,4 +1,5 @@ import { PanelModel } from '@grafana/data'; + import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations'; describe('Gauge Panel Migrations', () => { diff --git a/public/app/plugins/panel/gauge/GaugeMigrations.ts b/public/app/plugins/panel/gauge/GaugeMigrations.ts index d4b619aa20c..343e4a00d36 100644 --- a/public/app/plugins/panel/gauge/GaugeMigrations.ts +++ b/public/app/plugins/panel/gauge/GaugeMigrations.ts @@ -1,5 +1,6 @@ -import { sharedSingleStatPanelChangedHandler, sharedSingleStatMigrationHandler } from '@grafana/ui'; import { PanelModel } from '@grafana/data'; +import { sharedSingleStatPanelChangedHandler, sharedSingleStatMigrationHandler } from '@grafana/ui'; + import { GaugeOptions } from './types'; // This is called when the panel first loads diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 950ae77e762..c4b38b6b614 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -1,12 +1,14 @@ import React, { PureComponent } from 'react'; + import { FieldDisplay, getFieldDisplayValues, PanelProps } from '@grafana/data'; import { DataLinksContextMenu, Gauge, VizRepeater, VizRepeaterRenderValueProps } from '@grafana/ui'; import { DataLinksContextMenuApi } from '@grafana/ui/src/components/DataLinks/DataLinksContextMenu'; - import { config } from 'app/core/config'; -import { GaugeOptions } from './types'; + import { clearNameForSingleSeries } from '../bargauge/BarGaugePanel'; +import { GaugeOptions } from './types'; + export class GaugePanel extends PureComponent> { renderComponent = ( valueProps: VizRepeaterRenderValueProps, diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 4b1cd0d52fa..f4bf37140c9 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -1,10 +1,12 @@ import { PanelPlugin } from '@grafana/data'; -import { GaugePanel } from './GaugePanel'; -import { GaugeOptions } from './types'; -import { addOrientationOption, addStandardDataReduceOptions } from '../stat/types'; -import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations'; import { commonOptionsBuilder } from '@grafana/ui'; + +import { addOrientationOption, addStandardDataReduceOptions } from '../stat/types'; + +import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations'; +import { GaugePanel } from './GaugePanel'; import { GaugeSuggestionsSupplier } from './suggestions'; +import { GaugeOptions } from './types'; export const plugin = new PanelPlugin(GaugePanel) .useFieldConfig() diff --git a/public/app/plugins/panel/gauge/suggestions.ts b/public/app/plugins/panel/gauge/suggestions.ts index 5863509b416..edad77f745a 100644 --- a/public/app/plugins/panel/gauge/suggestions.ts +++ b/public/app/plugins/panel/gauge/suggestions.ts @@ -1,5 +1,6 @@ import { ThresholdsMode, VisualizationSuggestionsBuilder } from '@grafana/data'; import { SuggestionName } from 'app/types/suggestions'; + import { GaugeOptions } from './types'; export class GaugeSuggestionsSupplier { diff --git a/public/app/plugins/panel/geomap/GeomapOverlay.tsx b/public/app/plugins/panel/geomap/GeomapOverlay.tsx index b7caeba1387..af999908a77 100644 --- a/public/app/plugins/panel/geomap/GeomapOverlay.tsx +++ b/public/app/plugins/panel/geomap/GeomapOverlay.tsx @@ -1,8 +1,9 @@ +import { css } from '@emotion/css'; import React, { PureComponent } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { config } from '@grafana/runtime'; import { stylesFactory } from '@grafana/ui'; -import { css } from '@emotion/css'; export interface OverlayProps { topRight?: React.ReactNode[]; diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index 420a3125a9c..090d38c562e 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -1,14 +1,20 @@ -import React, { Component, ReactNode } from 'react'; -import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from './layers/registry'; +import { css } from '@emotion/css'; +import { Global } from '@emotion/react'; +import { cloneDeep } from 'lodash'; import { Collection, Map as OpenLayersMap, MapBrowserEvent, PluggableMap, View } from 'ol'; +import { FeatureLike } from 'ol/Feature'; import Attribution from 'ol/control/Attribution'; -import Zoom from 'ol/control/Zoom'; import ScaleLine from 'ol/control/ScaleLine'; +import Zoom from 'ol/control/Zoom'; +import { Coordinate } from 'ol/coordinate'; import { createEmpty, extend, isEmpty } from 'ol/extent'; import { defaults as interactionDefaults } from 'ol/interaction'; +import MouseWheelZoom from 'ol/interaction/MouseWheelZoom'; import BaseLayer from 'ol/layer/Base'; import VectorLayer from 'ol/layer/Vector'; -import MouseWheelZoom from 'ol/interaction/MouseWheelZoom'; +import { fromLonLat, toLonLat } from 'ol/proj'; +import React, { Component, ReactNode } from 'react'; +import { Subject, Subscription } from 'rxjs'; import { PanelData, @@ -21,24 +27,18 @@ import { FrameGeometrySourceMode, } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { PanelContext, PanelContextRoot, stylesFactory } from '@grafana/ui'; +import { PanelEditExitedEvent } from 'app/types/events'; +import { GeomapOverlay, OverlayProps } from './GeomapOverlay'; +import { GeomapTooltip } from './GeomapTooltip'; +import { DebugOverlay } from './components/DebugOverlay'; +import { GeomapHoverPayload, GeomapLayerHover } from './event'; +import { getGlobalStyles } from './globalStyles'; +import { defaultMarkersConfig, MARKERS_LAYER_ID } from './layers/data/markersLayer'; +import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from './layers/registry'; import { ControlsOptions, GeomapPanelOptions, MapLayerState, MapViewConfig } from './types'; import { centerPointRegistry, MapCenterID } from './view'; -import { fromLonLat, toLonLat } from 'ol/proj'; -import { Coordinate } from 'ol/coordinate'; -import { css } from '@emotion/css'; -import { PanelContext, PanelContextRoot, stylesFactory } from '@grafana/ui'; -import { GeomapOverlay, OverlayProps } from './GeomapOverlay'; -import { DebugOverlay } from './components/DebugOverlay'; -import { getGlobalStyles } from './globalStyles'; -import { Global } from '@emotion/react'; -import { GeomapHoverPayload, GeomapLayerHover } from './event'; -import { Subject, Subscription } from 'rxjs'; -import { PanelEditExitedEvent } from 'app/types/events'; -import { defaultMarkersConfig, MARKERS_LAYER_ID } from './layers/data/markersLayer'; -import { cloneDeep } from 'lodash'; -import { GeomapTooltip } from './GeomapTooltip'; -import { FeatureLike } from 'ol/Feature'; // Allows multiple panels to share the same view instance let sharedView: View | undefined = undefined; diff --git a/public/app/plugins/panel/geomap/GeomapTooltip.tsx b/public/app/plugins/panel/geomap/GeomapTooltip.tsx index 6dd9578e2d5..90b1c2c5b80 100644 --- a/public/app/plugins/panel/geomap/GeomapTooltip.tsx +++ b/public/app/plugins/panel/geomap/GeomapTooltip.tsx @@ -1,7 +1,8 @@ -import React, { createRef } from 'react'; -import { VizTooltipContainer } from '@grafana/ui'; import { useDialog } from '@react-aria/dialog'; import { useOverlay } from '@react-aria/overlays'; +import React, { createRef } from 'react'; + +import { VizTooltipContainer } from '@grafana/ui'; import { ComplexDataHoverView } from './components/ComplexDataHoverView'; import { GeomapHoverPayload } from './event'; diff --git a/public/app/plugins/panel/geomap/components/ComplexDataHoverView.tsx b/public/app/plugins/panel/geomap/components/ComplexDataHoverView.tsx index 75ea827ec2c..767f3f29b8f 100644 --- a/public/app/plugins/panel/geomap/components/ComplexDataHoverView.tsx +++ b/public/app/plugins/panel/geomap/components/ComplexDataHoverView.tsx @@ -1,10 +1,12 @@ import React, { useState } from 'react'; -import { GeomapLayerHover } from '../event'; -import { DataHoverTabs } from './DataHoverTabs'; -import { DataHoverRows } from './DataHoverRows'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import { GeomapLayerHover } from '../event'; + +import { DataHoverRows } from './DataHoverRows'; +import { DataHoverTabs } from './DataHoverTabs'; + export interface Props { layers?: GeomapLayerHover[]; isOpen: boolean; diff --git a/public/app/plugins/panel/geomap/components/DataHoverRow.tsx b/public/app/plugins/panel/geomap/components/DataHoverRow.tsx index ebb1d8b8499..5c8a38a6480 100644 --- a/public/app/plugins/panel/geomap/components/DataHoverRow.tsx +++ b/public/app/plugins/panel/geomap/components/DataHoverRow.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { FeatureLike } from 'ol/Feature'; +import React from 'react'; + import { ArrayDataFrame, DataFrame } from '@grafana/data'; import { DataHoverView } from './DataHoverView'; diff --git a/public/app/plugins/panel/geomap/components/DataHoverRows.tsx b/public/app/plugins/panel/geomap/components/DataHoverRows.tsx index 0a234a392c4..aac2f77dc8d 100644 --- a/public/app/plugins/panel/geomap/components/DataHoverRows.tsx +++ b/public/app/plugins/panel/geomap/components/DataHoverRows.tsx @@ -1,12 +1,14 @@ -import React, { useState } from 'react'; -import { Collapse, TabContent, useStyles2 } from '@grafana/ui'; -import { DataFrame, FieldType, getFieldDisplayName, GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import { isString } from 'lodash'; import { FeatureLike } from 'ol/Feature'; +import React, { useState } from 'react'; + +import { DataFrame, FieldType, getFieldDisplayName, GrafanaTheme2 } from '@grafana/data'; +import { Collapse, TabContent, useStyles2 } from '@grafana/ui'; import { GeomapLayerHover } from '../event'; + import { DataHoverRow } from './DataHoverRow'; -import { isString } from 'lodash'; type Props = { layers: GeomapLayerHover[]; diff --git a/public/app/plugins/panel/geomap/components/DataHoverTabs.tsx b/public/app/plugins/panel/geomap/components/DataHoverTabs.tsx index 8ad828f2496..4014d0308f8 100644 --- a/public/app/plugins/panel/geomap/components/DataHoverTabs.tsx +++ b/public/app/plugins/panel/geomap/components/DataHoverTabs.tsx @@ -1,4 +1,5 @@ import React, { Dispatch, SetStateAction } from 'react'; + import { Tab, TabsBar } from '@grafana/ui'; import { GeomapLayerHover } from '../event'; diff --git a/public/app/plugins/panel/geomap/components/DataHoverView.tsx b/public/app/plugins/panel/geomap/components/DataHoverView.tsx index 5f6977b3231..6a7a750ee4e 100644 --- a/public/app/plugins/panel/geomap/components/DataHoverView.tsx +++ b/public/app/plugins/panel/geomap/components/DataHoverView.tsx @@ -1,5 +1,6 @@ +import { css } from '@emotion/css'; import React from 'react'; -import { LinkButton, useStyles2, VerticalGroup } from '@grafana/ui'; + import { arrayUtils, DataFrame, @@ -9,8 +10,8 @@ import { GrafanaTheme2, LinkModel, } from '@grafana/data'; -import { css } from '@emotion/css'; import { SortOrder } from '@grafana/schema'; +import { LinkButton, useStyles2, VerticalGroup } from '@grafana/ui'; export interface Props { data?: DataFrame; // source data diff --git a/public/app/plugins/panel/geomap/components/DebugOverlay.tsx b/public/app/plugins/panel/geomap/components/DebugOverlay.tsx index c47d20aba4c..4cd85540d34 100644 --- a/public/app/plugins/panel/geomap/components/DebugOverlay.tsx +++ b/public/app/plugins/panel/geomap/components/DebugOverlay.tsx @@ -1,12 +1,13 @@ -import React, { PureComponent } from 'react'; -import { Map } from 'ol'; -import { transform } from 'ol/proj'; -import { stylesFactory } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; -import { config } from 'app/core/config'; -import tinycolor from 'tinycolor2'; +import { Map } from 'ol'; import { Coordinate } from 'ol/coordinate'; +import { transform } from 'ol/proj'; +import React, { PureComponent } from 'react'; +import tinycolor from 'tinycolor2'; + +import { GrafanaTheme } from '@grafana/data'; +import { stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; interface Props { map: Map; diff --git a/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx b/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx index c3d1cf1f7f1..369820222f3 100644 --- a/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx @@ -1,11 +1,13 @@ import React, { FC, useCallback } from 'react'; -import { Button, useTheme2 } from '@grafana/ui'; -import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data'; -import { FeatureStyleConfig } from '../types'; +import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data'; +import { Button, useTheme2 } from '@grafana/ui'; + import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer'; -import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor'; import { defaultStyleConfig } from '../style/types'; +import { FeatureStyleConfig } from '../types'; + +import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor'; export const GeomapStyleRulesEditor: FC> = (props) => { const { value, onChange, context, item } = props; diff --git a/public/app/plugins/panel/geomap/editor/LayersEditor.tsx b/public/app/plugins/panel/geomap/editor/LayersEditor.tsx index c2cfccc89dc..afad49655f9 100644 --- a/public/app/plugins/panel/geomap/editor/LayersEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/LayersEditor.tsx @@ -1,15 +1,17 @@ import React from 'react'; -import { Container } from '@grafana/ui'; -import { StandardEditorProps } from '@grafana/data'; import { DropResult } from 'react-beautiful-dnd'; -import { GeomapPanelOptions, MapLayerState } from '../types'; -import { GeomapInstanceState } from '../GeomapPanel'; -import { geomapLayerRegistry } from '../layers/registry'; -import { dataLayerFilter } from './layerEditor'; +import { StandardEditorProps } from '@grafana/data'; +import { Container } from '@grafana/ui'; import { AddLayerButton } from 'app/core/components/Layers/AddLayerButton'; import { LayerDragDropList } from 'app/core/components/Layers/LayerDragDropList'; +import { GeomapInstanceState } from '../GeomapPanel'; +import { geomapLayerRegistry } from '../layers/registry'; +import { GeomapPanelOptions, MapLayerState } from '../types'; + +import { dataLayerFilter } from './layerEditor'; + type LayersEditorProps = StandardEditorProps; export const LayersEditor = (props: LayersEditorProps) => { diff --git a/public/app/plugins/panel/geomap/editor/MapViewEditor.tsx b/public/app/plugins/panel/geomap/editor/MapViewEditor.tsx index de640f515f4..07109e27eda 100644 --- a/public/app/plugins/panel/geomap/editor/MapViewEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/MapViewEditor.tsx @@ -1,11 +1,13 @@ +import { toLonLat } from 'ol/proj'; import React, { FC, useMemo, useCallback } from 'react'; + import { StandardEditorProps, SelectableValue } from '@grafana/data'; import { Button, InlineField, InlineFieldRow, Select, VerticalGroup } from '@grafana/ui'; +import { NumberInput } from 'app/features/dimensions/editors/NumberInput'; + +import { GeomapInstanceState } from '../GeomapPanel'; import { GeomapPanelOptions, MapViewConfig } from '../types'; import { centerPointRegistry, MapCenterID } from '../view'; -import { NumberInput } from 'app/features/dimensions/editors/NumberInput'; -import { toLonLat } from 'ol/proj'; -import { GeomapInstanceState } from '../GeomapPanel'; export const MapViewEditor: FC> = ({ value, diff --git a/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx b/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx index 34681cc9330..7bfcf7850f6 100644 --- a/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx @@ -1,18 +1,20 @@ -import React, { FC, useCallback, useMemo } from 'react'; -import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data'; -import { ComparisonOperation, FeatureStyleConfig } from '../types'; -import { Button, InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui'; import { css } from '@emotion/css'; -import { StyleEditor } from '../layers/data/StyleEditor'; -import { defaultStyleConfig, StyleConfig } from '../style/types'; -import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer'; -import { Observable } from 'rxjs'; -import { useObservable } from 'react-use'; -import { getUniqueFeatureValues, LayerContentInfo } from '../utils/getFeatures'; import { FeatureLike } from 'ol/Feature'; -import { getSelectionInfo } from '../utils/selection'; +import React, { FC, useCallback, useMemo } from 'react'; +import { useObservable } from 'react-use'; +import { Observable } from 'rxjs'; + +import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data'; +import { Button, InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui'; import { NumberInput } from 'app/features/dimensions/editors/NumberInput'; +import { StyleEditor } from '../layers/data/StyleEditor'; +import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer'; +import { defaultStyleConfig, StyleConfig } from '../style/types'; +import { ComparisonOperation, FeatureStyleConfig } from '../types'; +import { getUniqueFeatureValues, LayerContentInfo } from '../utils/getFeatures'; +import { getSelectionInfo } from '../utils/selection'; + export interface StyleRuleEditorSettings { features: Observable; layerInfo: Observable; diff --git a/public/app/plugins/panel/geomap/editor/layerEditor.tsx b/public/app/plugins/panel/geomap/editor/layerEditor.tsx index 22103c678ac..3ddeb4f0bd3 100644 --- a/public/app/plugins/panel/geomap/editor/layerEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/layerEditor.tsx @@ -1,13 +1,15 @@ -import { FrameGeometrySourceMode, MapLayerOptions, MapLayerRegistryItem, PluginState } from '@grafana/data'; -import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from '../layers/registry'; -import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; -import { defaultMarkersConfig } from '../layers/data/markersLayer'; -import { hasAlphaPanels } from 'app/core/config'; -import { MapLayerState } from '../types'; import { get as lodashGet, isEqual } from 'lodash'; + +import { FrameGeometrySourceMode, MapLayerOptions, MapLayerRegistryItem, PluginState } from '@grafana/data'; +import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; +import { hasAlphaPanels } from 'app/core/config'; import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils'; import { addLocationFields } from 'app/features/geo/editor/locationEditor'; +import { defaultMarkersConfig } from '../layers/data/markersLayer'; +import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from '../layers/registry'; +import { MapLayerState } from '../types'; + export interface LayerEditorOptions { state: MapLayerState; category: string[]; diff --git a/public/app/plugins/panel/geomap/event.ts b/public/app/plugins/panel/geomap/event.ts index c93478d30fc..885d5cd14de 100644 --- a/public/app/plugins/panel/geomap/event.ts +++ b/public/app/plugins/panel/geomap/event.ts @@ -1,5 +1,7 @@ import { FeatureLike } from 'ol/Feature'; + import { DataHoverPayload } from '@grafana/data'; + import { MapLayerState } from './types'; export interface GeomapLayerHover { diff --git a/public/app/plugins/panel/geomap/globalStyles.ts b/public/app/plugins/panel/geomap/globalStyles.ts index 0610cd7ea86..2d0f6f86001 100644 --- a/public/app/plugins/panel/geomap/globalStyles.ts +++ b/public/app/plugins/panel/geomap/globalStyles.ts @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; + import { GrafanaTheme2 } from '@grafana/data'; import 'ol/ol.css'; diff --git a/public/app/plugins/panel/geomap/layers/basemaps/carto.ts b/public/app/plugins/panel/geomap/layers/basemaps/carto.ts index ed903deb7d4..7245507dc47 100644 --- a/public/app/plugins/panel/geomap/layers/basemaps/carto.ts +++ b/public/app/plugins/panel/geomap/layers/basemaps/carto.ts @@ -1,7 +1,8 @@ -import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2 } from '@grafana/data'; import Map from 'ol/Map'; -import XYZ from 'ol/source/XYZ'; import TileLayer from 'ol/layer/Tile'; +import XYZ from 'ol/source/XYZ'; + +import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2 } from '@grafana/data'; // https://carto.com/help/building-maps/basemap-list/ diff --git a/public/app/plugins/panel/geomap/layers/basemaps/esri.ts b/public/app/plugins/panel/geomap/layers/basemaps/esri.ts index 06ebaadd42b..e8873235e6a 100644 --- a/public/app/plugins/panel/geomap/layers/basemaps/esri.ts +++ b/public/app/plugins/panel/geomap/layers/basemaps/esri.ts @@ -1,5 +1,7 @@ -import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, RegistryItem, Registry } from '@grafana/data'; import Map from 'ol/Map'; + +import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, RegistryItem, Registry } from '@grafana/data'; + import { xyzTiles, defaultXYZConfig, XYZConfig } from './generic'; interface PublicServiceItem extends RegistryItem { diff --git a/public/app/plugins/panel/geomap/layers/basemaps/generic.ts b/public/app/plugins/panel/geomap/layers/basemaps/generic.ts index 8bb50454719..622a10768e0 100644 --- a/public/app/plugins/panel/geomap/layers/basemaps/generic.ts +++ b/public/app/plugins/panel/geomap/layers/basemaps/generic.ts @@ -1,7 +1,8 @@ -import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2 } from '@grafana/data'; import Map from 'ol/Map'; -import XYZ from 'ol/source/XYZ'; import TileLayer from 'ol/layer/Tile'; +import XYZ from 'ol/source/XYZ'; + +import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2 } from '@grafana/data'; export interface XYZConfig { url: string; diff --git a/public/app/plugins/panel/geomap/layers/basemaps/osm.ts b/public/app/plugins/panel/geomap/layers/basemaps/osm.ts index 7fcf753a660..e51e181315e 100644 --- a/public/app/plugins/panel/geomap/layers/basemaps/osm.ts +++ b/public/app/plugins/panel/geomap/layers/basemaps/osm.ts @@ -1,7 +1,8 @@ -import { MapLayerRegistryItem, MapLayerOptions } from '@grafana/data'; import Map from 'ol/Map'; -import OSM from 'ol/source/OSM'; import TileLayer from 'ol/layer/Tile'; +import OSM from 'ol/source/OSM'; + +import { MapLayerRegistryItem, MapLayerOptions } from '@grafana/data'; export const standard: MapLayerRegistryItem = { id: 'osm-standard', diff --git a/public/app/plugins/panel/geomap/layers/registry.ts b/public/app/plugins/panel/geomap/layers/registry.ts index 123828a9de1..b346e552f23 100644 --- a/public/app/plugins/panel/geomap/layers/registry.ts +++ b/public/app/plugins/panel/geomap/layers/registry.ts @@ -1,8 +1,10 @@ -import { MapLayerRegistryItem, Registry, MapLayerOptions, GrafanaTheme2 } from '@grafana/data'; import Map from 'ol/Map'; -import { carto } from './basemaps/carto'; + +import { MapLayerRegistryItem, Registry, MapLayerOptions, GrafanaTheme2 } from '@grafana/data'; import { config } from 'app/core/config'; + import { basemapLayers } from './basemaps'; +import { carto } from './basemaps/carto'; import { dataLayers } from './data'; export const DEFAULT_BASEMAP_CONFIG: MapLayerOptions = { diff --git a/public/app/plugins/panel/geomap/migrations.test.ts b/public/app/plugins/panel/geomap/migrations.test.ts index 618051abb74..a100738d189 100644 --- a/public/app/plugins/panel/geomap/migrations.test.ts +++ b/public/app/plugins/panel/geomap/migrations.test.ts @@ -1,4 +1,5 @@ import { PanelModel, FieldConfigSource } from '@grafana/data'; + import { mapMigrationHandler, mapPanelChangedHandler } from './migrations'; describe('Worldmap Migrations', () => { let prevFieldConfig: FieldConfigSource; diff --git a/public/app/plugins/panel/geomap/migrations.ts b/public/app/plugins/panel/geomap/migrations.ts index 07a8a871cfa..68c36caa859 100644 --- a/public/app/plugins/panel/geomap/migrations.ts +++ b/public/app/plugins/panel/geomap/migrations.ts @@ -1,6 +1,8 @@ +import { cloneDeep } from 'lodash'; + import { FieldConfigSource, PanelModel, PanelTypeChangedHandler, Threshold, ThresholdsMode } from '@grafana/data'; import { ResourceDimensionMode } from 'app/features/dimensions'; -import { cloneDeep } from 'lodash'; + import { MarkersConfig } from './layers/data/markersLayer'; import { getMarkerAsPath } from './style/markers'; import { defaultStyleConfig } from './style/types'; diff --git a/public/app/plugins/panel/geomap/module.tsx b/public/app/plugins/panel/geomap/module.tsx index 66edb5fe1f9..655d34ae9ce 100644 --- a/public/app/plugins/panel/geomap/module.tsx +++ b/public/app/plugins/panel/geomap/module.tsx @@ -1,14 +1,16 @@ import React from 'react'; + import { PanelPlugin } from '@grafana/data'; -import { GeomapInstanceState, GeomapPanel } from './GeomapPanel'; -import { MapViewEditor } from './editor/MapViewEditor'; -import { defaultView, GeomapPanelOptions } from './types'; -import { mapPanelChangedHandler, mapMigrationHandler } from './migrations'; -import { getLayerEditor } from './editor/layerEditor'; -import { LayersEditor } from './editor/LayersEditor'; import { config } from '@grafana/runtime'; import { commonOptionsBuilder } from '@grafana/ui'; +import { GeomapInstanceState, GeomapPanel } from './GeomapPanel'; +import { LayersEditor } from './editor/LayersEditor'; +import { MapViewEditor } from './editor/MapViewEditor'; +import { getLayerEditor } from './editor/layerEditor'; +import { mapPanelChangedHandler, mapMigrationHandler } from './migrations'; +import { defaultView, GeomapPanelOptions } from './types'; + export const plugin = new PanelPlugin(GeomapPanel) .setNoPadding() .setPanelChangeHandler(mapPanelChangedHandler) diff --git a/public/app/plugins/panel/geomap/style/markers.ts b/public/app/plugins/panel/geomap/style/markers.ts index 5ebf33f1f67..309dcefed97 100644 --- a/public/app/plugins/panel/geomap/style/markers.ts +++ b/public/app/plugins/panel/geomap/style/markers.ts @@ -1,9 +1,11 @@ import { Fill, RegularShape, Stroke, Circle, Style, Icon, Text } from 'ol/style'; -import { Registry, RegistryItem } from '@grafana/data'; -import { defaultStyleConfig, DEFAULT_SIZE, StyleConfigValues, StyleMaker } from './types'; -import { getPublicOrAbsoluteUrl } from 'app/features/dimensions'; import tinycolor from 'tinycolor2'; + +import { Registry, RegistryItem } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { getPublicOrAbsoluteUrl } from 'app/features/dimensions'; + +import { defaultStyleConfig, DEFAULT_SIZE, StyleConfigValues, StyleMaker } from './types'; interface SymbolMaker extends RegistryItem { aliasIds: string[]; diff --git a/public/app/plugins/panel/geomap/style/types.ts b/public/app/plugins/panel/geomap/style/types.ts index 825e8fbe8f2..5192dbd9afa 100644 --- a/public/app/plugins/panel/geomap/style/types.ts +++ b/public/app/plugins/panel/geomap/style/types.ts @@ -1,3 +1,5 @@ +import { Style } from 'ol/style'; + import { ColorDimensionConfig, DimensionSupplier, @@ -8,7 +10,6 @@ import { ScalarDimensionMode, TextDimensionConfig, } from 'app/features/dimensions'; -import { Style } from 'ol/style'; export enum GeometryTypeId { Point = 'point', diff --git a/public/app/plugins/panel/geomap/style/utils.test.ts b/public/app/plugins/panel/geomap/style/utils.test.ts index b046c705797..ddb4cc4f0e8 100644 --- a/public/app/plugins/panel/geomap/style/utils.test.ts +++ b/public/app/plugins/panel/geomap/style/utils.test.ts @@ -1,4 +1,5 @@ import { ResourceDimensionMode } from 'app/features/dimensions'; + import { StyleConfig } from './types'; import { getStyleConfigState } from './utils'; diff --git a/public/app/plugins/panel/geomap/style/utils.ts b/public/app/plugins/panel/geomap/style/utils.ts index 601b3f5bf38..607ab05f2d8 100644 --- a/public/app/plugins/panel/geomap/style/utils.ts +++ b/public/app/plugins/panel/geomap/style/utils.ts @@ -1,5 +1,6 @@ import { config } from '@grafana/runtime'; import { TextDimensionMode } from 'app/features/dimensions'; + import { getMarkerMaker } from './markers'; import { defaultStyleConfig, StyleConfig, StyleConfigFields, StyleConfigState } from './types'; diff --git a/public/app/plugins/panel/geomap/types.ts b/public/app/plugins/panel/geomap/types.ts index 575ba4f333d..521ceb498e9 100644 --- a/public/app/plugins/panel/geomap/types.ts +++ b/public/app/plugins/panel/geomap/types.ts @@ -1,12 +1,14 @@ +import { FeatureLike } from 'ol/Feature'; +import BaseLayer from 'ol/layer/Base'; +import Units from 'ol/proj/Units'; +import { Subject } from 'rxjs'; + import { MapLayerHandler, MapLayerOptions } from '@grafana/data'; import { HideableFieldConfig } from '@grafana/schema'; import { LayerElement } from 'app/core/components/Layers/types'; -import BaseLayer from 'ol/layer/Base'; -import Units from 'ol/proj/Units'; + import { StyleConfig } from './style/types'; import { MapCenterID } from './view'; -import { Subject } from 'rxjs'; -import { FeatureLike } from 'ol/Feature'; export interface ControlsOptions { // Zoom (upper left) diff --git a/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts index 65e27150ce9..97c5e6086f4 100644 --- a/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts +++ b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts @@ -1,5 +1,7 @@ import { Feature } from 'ol'; + import { ComparisonOperation } from '../types'; + import { checkFeatureMatchesStyleRule } from './checkFeatureMatchesStyleRule'; describe('check if feature matches style rule', () => { diff --git a/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts index b5ed69dccb2..19027280de4 100644 --- a/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts +++ b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts @@ -1,4 +1,5 @@ import { FeatureLike } from 'ol/Feature'; + import { FeatureRuleConfig, ComparisonOperation } from '../types'; /** diff --git a/public/app/plugins/panel/geomap/utils/getFeatures.test.ts b/public/app/plugins/panel/geomap/utils/getFeatures.test.ts index fbc8a25a9a6..6bc61033399 100644 --- a/public/app/plugins/panel/geomap/utils/getFeatures.test.ts +++ b/public/app/plugins/panel/geomap/utils/getFeatures.test.ts @@ -1,6 +1,8 @@ import { Feature } from 'ol'; import { Point } from 'ol/geom'; + import { GeometryTypeId } from '../style/types'; + import { getLayerPropertyInfo, getUniqueFeatureValues } from './getFeatures'; describe('get features utils', () => { diff --git a/public/app/plugins/panel/geomap/utils/getFeatures.ts b/public/app/plugins/panel/geomap/utils/getFeatures.ts index 49ea66b85ce..48f37446173 100644 --- a/public/app/plugins/panel/geomap/utils/getFeatures.ts +++ b/public/app/plugins/panel/geomap/utils/getFeatures.ts @@ -1,5 +1,7 @@ -import { SelectableValue } from '@grafana/data'; import { FeatureLike } from 'ol/Feature'; + +import { SelectableValue } from '@grafana/data'; + import { GeometryTypeId } from '../style/types'; export interface LayerContentInfo { diff --git a/public/app/plugins/panel/gettingstarted/GettingStarted.tsx b/public/app/plugins/panel/gettingstarted/GettingStarted.tsx index 9f23ec1fd3a..b432ecd31ff 100644 --- a/public/app/plugins/panel/gettingstarted/GettingStarted.tsx +++ b/public/app/plugins/panel/gettingstarted/GettingStarted.tsx @@ -1,12 +1,14 @@ // Libraries -import React, { PureComponent } from 'react'; -import { PanelProps } from '@grafana/data'; -import { Button, Spinner, stylesFactory } from '@grafana/ui'; -import { config } from '@grafana/runtime'; import { css, cx } from '@emotion/css'; +import React, { PureComponent } from 'react'; + +import { PanelProps } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { Button, Spinner, stylesFactory } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { backendSrv } from 'app/core/services/backend_srv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; + import { Step } from './components/Step'; import { getSteps } from './steps'; import { Card, SetupStep } from './types'; diff --git a/public/app/plugins/panel/gettingstarted/components/DocsCard.tsx b/public/app/plugins/panel/gettingstarted/components/DocsCard.tsx index 2a143c61045..d8e85444463 100644 --- a/public/app/plugins/panel/gettingstarted/components/DocsCard.tsx +++ b/public/app/plugins/panel/gettingstarted/components/DocsCard.tsx @@ -1,8 +1,11 @@ -import React, { FC } from 'react'; -import { Card } from '../types'; -import { Icon, stylesFactory, useTheme } from '@grafana/ui'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; +import { Icon, stylesFactory, useTheme } from '@grafana/ui'; + +import { Card } from '../types'; + import { cardContent, cardStyle, iconStyle } from './sharedStyles'; interface Props { diff --git a/public/app/plugins/panel/gettingstarted/components/Step.tsx b/public/app/plugins/panel/gettingstarted/components/Step.tsx index ea2dcbfd6fb..c4e8e26013a 100644 --- a/public/app/plugins/panel/gettingstarted/components/Step.tsx +++ b/public/app/plugins/panel/gettingstarted/components/Step.tsx @@ -1,10 +1,13 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme } from '@grafana/ui'; -import { TutorialCard } from './TutorialCard'; + import { Card, SetupStep, TutorialCardType } from '../types'; + import { DocsCard } from './DocsCard'; +import { TutorialCard } from './TutorialCard'; interface Props { step: SetupStep; diff --git a/public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx b/public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx index d0d4dc38ebb..53006f42c63 100644 --- a/public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx +++ b/public/app/plugins/panel/gettingstarted/components/TutorialCard.tsx @@ -1,11 +1,14 @@ +import { css } from '@emotion/css'; import React, { FC, MouseEvent } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { Icon, stylesFactory, useTheme } from '@grafana/ui'; -import { css } from '@emotion/css'; import store from 'app/core/store'; -import { cardContent, cardStyle, iconStyle } from './sharedStyles'; + import { TutorialCardType } from '../types'; +import { cardContent, cardStyle, iconStyle } from './sharedStyles'; + interface Props { card: TutorialCardType; } diff --git a/public/app/plugins/panel/gettingstarted/components/sharedStyles.ts b/public/app/plugins/panel/gettingstarted/components/sharedStyles.ts index b89966f84de..8d37e07d00f 100644 --- a/public/app/plugins/panel/gettingstarted/components/sharedStyles.ts +++ b/public/app/plugins/panel/gettingstarted/components/sharedStyles.ts @@ -1,5 +1,6 @@ -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; + +import { GrafanaTheme } from '@grafana/data'; import { stylesFactory } from '@grafana/ui'; export const cardStyle = stylesFactory((theme: GrafanaTheme, complete: boolean) => { diff --git a/public/app/plugins/panel/gettingstarted/module.ts b/public/app/plugins/panel/gettingstarted/module.ts index 6ce86a581aa..2cac3c7922d 100644 --- a/public/app/plugins/panel/gettingstarted/module.ts +++ b/public/app/plugins/panel/gettingstarted/module.ts @@ -1,4 +1,5 @@ import { PanelPlugin } from '@grafana/data'; + import { GettingStarted } from './GettingStarted'; // Simplest possible panel plugin diff --git a/public/app/plugins/panel/gettingstarted/steps.ts b/public/app/plugins/panel/gettingstarted/steps.ts index b69fb9c9d59..2d00cdc2699 100644 --- a/public/app/plugins/panel/gettingstarted/steps.ts +++ b/public/app/plugins/panel/gettingstarted/steps.ts @@ -1,6 +1,7 @@ -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getBackendSrv } from 'app/core/services/backend_srv'; import store from 'app/core/store'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; + import { SetupStep } from './types'; const step1TutorialTitle = 'Grafana fundamentals'; diff --git a/public/app/plugins/panel/graph/GraphContextMenuCtrl.ts b/public/app/plugins/panel/graph/GraphContextMenuCtrl.ts index 20aed113d9e..3dea4edcb5c 100644 --- a/public/app/plugins/panel/graph/GraphContextMenuCtrl.ts +++ b/public/app/plugins/panel/graph/GraphContextMenuCtrl.ts @@ -1,5 +1,5 @@ -import { MenuItemProps } from '@grafana/ui'; import { FlotDataPoint } from '@grafana/data'; +import { MenuItemProps } from '@grafana/ui'; export class GraphContextMenuCtrl { private source?: FlotDataPoint | null; diff --git a/public/app/plugins/panel/graph/GraphMigrations.test.ts b/public/app/plugins/panel/graph/GraphMigrations.test.ts index a6a2a71f7ba..c9562ee74d9 100644 --- a/public/app/plugins/panel/graph/GraphMigrations.test.ts +++ b/public/app/plugins/panel/graph/GraphMigrations.test.ts @@ -1,4 +1,5 @@ import { PanelModel, FieldConfigSource, DataQuery } from '@grafana/data'; + import { graphPanelMigrationHandler } from './GraphMigrations'; describe('Graph Panel Migrations', () => { diff --git a/public/app/plugins/panel/graph/Legend/Legend.tsx b/public/app/plugins/panel/graph/Legend/Legend.tsx index ae28086d48e..37a72041840 100644 --- a/public/app/plugins/panel/graph/Legend/Legend.tsx +++ b/public/app/plugins/panel/graph/Legend/Legend.tsx @@ -1,7 +1,9 @@ import { sortBy as _sortBy } from 'lodash'; import React, { PureComponent } from 'react'; -import { TimeSeries } from 'app/core/core'; + import { CustomScrollbar, Icon } from '@grafana/ui'; +import { TimeSeries } from 'app/core/core'; + import { LegendStat, LegendItem, LEGEND_STATS } from './LegendSeriesItem'; interface LegendProps { diff --git a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx index d6fabd2db00..f64d00fc59b 100644 --- a/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx +++ b/public/app/plugins/panel/graph/Legend/LegendSeriesItem.tsx @@ -1,8 +1,9 @@ -import React, { PureComponent } from 'react'; import classNames from 'classnames'; -import { TimeSeries } from 'app/core/core'; -import { SeriesColorPicker, SeriesIcon } from '@grafana/ui'; +import React, { PureComponent } from 'react'; + import { selectors } from '@grafana/e2e-selectors'; +import { SeriesColorPicker, SeriesIcon } from '@grafana/ui'; +import { TimeSeries } from 'app/core/core'; export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'] as const; export type LegendStat = typeof LEGEND_STATS[number]; diff --git a/public/app/plugins/panel/graph/annotation_tooltip.ts b/public/app/plugins/panel/graph/annotation_tooltip.ts index b27a87861e1..56ba2773a4b 100644 --- a/public/app/plugins/panel/graph/annotation_tooltip.ts +++ b/public/app/plugins/panel/graph/annotation_tooltip.ts @@ -1,9 +1,10 @@ -import { isString, escape } from 'lodash'; import $ from 'jquery'; +import { isString, escape } from 'lodash'; + import coreModule from 'app/angular/core_module'; +import { ContextSrv } from 'app/core/services/context_srv'; import alertDef from 'app/features/alerting/state/alertDef'; import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { ContextSrv } from 'app/core/services/context_srv'; /** @ngInject */ export function annotationTooltipDirective( diff --git a/public/app/plugins/panel/graph/axes_editor.ts b/public/app/plugins/panel/graph/axes_editor.ts index c14cb94c3cc..909620f21fb 100644 --- a/public/app/plugins/panel/graph/axes_editor.ts +++ b/public/app/plugins/panel/graph/axes_editor.ts @@ -1,4 +1,5 @@ import { selectors } from '@grafana/e2e-selectors'; + import { GraphCtrl } from './module'; export class AxesEditorCtrl { diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index edd5973cccf..51d5e068f62 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -1,5 +1,5 @@ import { find } from 'lodash'; -import { colors } from '@grafana/ui'; + import { DataFrame, dateTime, @@ -10,9 +10,10 @@ import { getTimeField, TimeRange, } from '@grafana/data'; -import TimeSeries from 'app/core/time_series2'; -import config from 'app/core/config'; +import { colors } from '@grafana/ui'; import { applyNullInsertThreshold } from '@grafana/ui/src/components/GraphNG/nullInsertThreshold'; +import config from 'app/core/config'; +import TimeSeries from 'app/core/time_series2'; type Options = { dataList: DataFrame[]; diff --git a/public/app/plugins/panel/graph/event_editor.ts b/public/app/plugins/panel/graph/event_editor.ts index a2ea2f2972b..a19a2d2a874 100644 --- a/public/app/plugins/panel/graph/event_editor.ts +++ b/public/app/plugins/panel/graph/event_editor.ts @@ -1,10 +1,12 @@ import { cloneDeep, isNumber } from 'lodash'; -import { coreModule } from 'app/angular/core_module'; + import { AnnotationEvent, dateTime } from '@grafana/data'; +import { coreModule } from 'app/angular/core_module'; import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl'; + +import { contextSrv } from '../../../core/services/context_srv'; import { deleteAnnotation, saveAnnotation, updateAnnotation } from '../../../features/annotations/api'; import { getDashboardQueryRunner } from '../../../features/query/state/DashboardQueryRunner/DashboardQueryRunner'; -import { contextSrv } from '../../../core/services/context_srv'; export class EventEditorCtrl { // @ts-ignore initialized through Angular not constructor diff --git a/public/app/plugins/panel/graph/event_manager.ts b/public/app/plugins/panel/graph/event_manager.ts index 19797d0562a..a0f9345b85d 100644 --- a/public/app/plugins/panel/graph/event_manager.ts +++ b/public/app/plugins/panel/graph/event_manager.ts @@ -1,5 +1,7 @@ import { each, filter, keys } from 'lodash'; import tinycolor from 'tinycolor2'; + +import { AnnotationEvent } from '@grafana/data'; import { ALERTING_COLOR, DEFAULT_ANNOTATION_COLOR, @@ -10,8 +12,6 @@ import { } from '@grafana/ui'; import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl'; -import { AnnotationEvent } from '@grafana/data'; - export class EventManager { event: AnnotationEvent | null = null; editorOpen = false; diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 55692cf35be..13467292b78 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -10,23 +10,9 @@ import './jquery.flot.events'; import $ from 'jquery'; import { clone, find, flatten, isUndefined, map, max as _max, min as _min, sortBy as _sortBy, toNumber } from 'lodash'; -import { tickStep } from 'app/core/utils/ticks'; -import { updateLegendValues } from 'app/core/core'; -import { coreModule } from 'app/angular/core_module'; -import GraphTooltip from './graph_tooltip'; -import { ThresholdManager } from './threshold_manager'; -import { TimeRegionManager } from './time_region_manager'; -import { EventManager } from './event_manager'; -import { convertToHistogramData } from './histogram'; -import { alignYLevel } from './align_yaxes'; -import config from 'app/core/config'; import React from 'react'; import ReactDOM from 'react-dom'; -import { GraphLegendProps, Legend } from './Legend/Legend'; -import { GraphCtrl } from './module'; -import { graphTickFormatter, graphTimeFormat, IconName, MenuItemProps, MenuItemsGroup } from '@grafana/ui'; -import { provideTheme } from 'app/core/utils/ConfigProvider'; import { DataFrame, DataFrameView, @@ -49,11 +35,27 @@ import { PanelEvents, toUtc, } from '@grafana/data'; -import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; -import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { graphTickFormatter, graphTimeFormat, IconName, MenuItemProps, MenuItemsGroup } from '@grafana/ui'; +import { coreModule } from 'app/angular/core_module'; +import config from 'app/core/config'; +import { updateLegendValues } from 'app/core/core'; import { ContextSrv } from 'app/core/services/context_srv'; +import { provideTheme } from 'app/core/utils/ConfigProvider'; +import { tickStep } from 'app/core/utils/ticks'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; + import { DashboardModel } from '../../../features/dashboard/state'; + +import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; +import { GraphLegendProps, Legend } from './Legend/Legend'; +import { alignYLevel } from './align_yaxes'; +import { EventManager } from './event_manager'; +import GraphTooltip from './graph_tooltip'; +import { convertToHistogramData } from './histogram'; +import { GraphCtrl } from './module'; +import { ThresholdManager } from './threshold_manager'; +import { TimeRegionManager } from './time_region_manager'; import { isLegacyGraphHoverEvent } from './utils'; const LegendWithThemeProvider = provideTheme(Legend); diff --git a/public/app/plugins/panel/graph/graph_tooltip.ts b/public/app/plugins/panel/graph/graph_tooltip.ts index fcdef4e8078..86a90bc5789 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.ts +++ b/public/app/plugins/panel/graph/graph_tooltip.ts @@ -1,6 +1,5 @@ import $ from 'jquery'; -import { appEvents } from 'app/core/core'; -import { CoreEvents } from 'app/types'; + import { textUtil, systemDateFormats, @@ -8,6 +7,8 @@ import { LegacyGraphHoverEvent, DataHoverClearEvent, } from '@grafana/data'; +import { appEvents } from 'app/core/core'; +import { CoreEvents } from 'app/types'; export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) { const self = this; diff --git a/public/app/plugins/panel/graph/histogram.ts b/public/app/plugins/panel/graph/histogram.ts index f563fd4864f..17e8f69178a 100644 --- a/public/app/plugins/panel/graph/histogram.ts +++ b/public/app/plugins/panel/graph/histogram.ts @@ -1,6 +1,7 @@ -import TimeSeries from 'app/core/time_series2'; import { histogram } from 'd3'; +import TimeSeries from 'app/core/time_series2'; + /** * Convert series into array of series values. * @param data Array of series diff --git a/public/app/plugins/panel/graph/jquery.flot.events.ts b/public/app/plugins/panel/graph/jquery.flot.events.ts index af0b37248c1..6d680122795 100644 --- a/public/app/plugins/panel/graph/jquery.flot.events.ts +++ b/public/app/plugins/panel/graph/jquery.flot.events.ts @@ -2,6 +2,7 @@ import $ from 'jquery'; import { partition, each } from 'lodash'; //@ts-ignore import Drop from 'tether-drop'; + import { CreatePlotOverlay } from '@grafana/data'; import { getLegacyAngularInjector } from '@grafana/runtime'; diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index cd7a23f6b23..4d04007096a 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -5,30 +5,30 @@ import './time_regions_form'; import './annotation_tooltip'; import './event_editor'; -import template from './template'; +import { auto } from 'angular'; import { defaults, find, without } from 'lodash'; -import { DataProcessor } from './data_processor'; -import { axesEditorComponent } from './axes_editor'; +import { DataFrame, FieldConfigProperty, getColorForTheme, PanelEvents, PanelPlugin } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl'; import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; -import { DataFrame, FieldConfigProperty, getColorForTheme, PanelEvents, PanelPlugin } from '@grafana/data'; +import { ThresholdMapper } from 'app/features/alerting/state/ThresholdMapper'; +import { changePanelPlugin } from 'app/features/panel/state/actions'; +import { dispatch } from 'app/store/store'; + +import { appEvents } from '../../../core/core'; +import { loadSnapshotData } from '../../../features/dashboard/utils/loadSnapshotData'; +import { annotationsFromDataFrames } from '../../../features/query/state/DashboardQueryRunner/utils'; +import { ZoomOutEvent } from '../../../types/events'; import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; import { graphPanelMigrationHandler } from './GraphMigrations'; +import { axesEditorComponent } from './axes_editor'; +import { DataProcessor } from './data_processor'; +import template from './template'; import { DataWarning, GraphFieldConfig, GraphPanelOptions } from './types'; - -import { auto } from 'angular'; -import { locationService } from '@grafana/runtime'; import { getDataTimeRange } from './utils'; -import { changePanelPlugin } from 'app/features/panel/state/actions'; -import { dispatch } from 'app/store/store'; -import { ThresholdMapper } from 'app/features/alerting/state/ThresholdMapper'; -import { appEvents } from '../../../core/core'; -import { ZoomOutEvent } from '../../../types/events'; -import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl'; -import { loadSnapshotData } from '../../../features/dashboard/utils/loadSnapshotData'; -import { annotationsFromDataFrames } from '../../../features/query/state/DashboardQueryRunner/utils'; export class GraphCtrl extends MetricsPanelCtrl { static template = template; diff --git a/public/app/plugins/panel/graph/series_overrides_ctrl.ts b/public/app/plugins/panel/graph/series_overrides_ctrl.ts index 67b1fcdc710..356d4dde79d 100644 --- a/public/app/plugins/panel/graph/series_overrides_ctrl.ts +++ b/public/app/plugins/panel/graph/series_overrides_ctrl.ts @@ -1,6 +1,7 @@ import { map, each, isUndefined } from 'lodash'; -import coreModule from 'app/angular/core_module'; + import { textUtil } from '@grafana/data'; +import coreModule from 'app/angular/core_module'; /** @ngInject */ export function SeriesOverridesCtrl($scope: any, $element: JQuery, popoverSrv: any) { diff --git a/public/app/plugins/panel/graph/specs/data_processor.test.ts b/public/app/plugins/panel/graph/specs/data_processor.test.ts index 6f86c5df091..e0bc733f008 100644 --- a/public/app/plugins/panel/graph/specs/data_processor.test.ts +++ b/public/app/plugins/panel/graph/specs/data_processor.test.ts @@ -1,6 +1,7 @@ -import { DataProcessor } from '../data_processor'; import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; +import { DataProcessor } from '../data_processor'; + describe('Graph DataProcessor', () => { const panel: any = { xaxis: { mode: 'series' }, diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts index a902fc3737a..8ef8e73ff1a 100644 --- a/public/app/plugins/panel/graph/specs/graph.test.ts +++ b/public/app/plugins/panel/graph/specs/graph.test.ts @@ -1,13 +1,14 @@ -import { GraphCtrl } from '../module'; +import $ from 'jquery'; + +import { dateTime, EventBusSrv } from '@grafana/data'; import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl'; import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; import config from 'app/core/config'; - import TimeSeries from 'app/core/time_series2'; -import $ from 'jquery'; -import { graphDirective, GraphElement } from '../graph'; -import { dateTime, EventBusSrv } from '@grafana/data'; + import { DashboardModel } from '../../../../features/dashboard/state'; +import { graphDirective, GraphElement } from '../graph'; +import { GraphCtrl } from '../module'; jest.mock('../event_manager', () => ({ EventManager: class EventManagerMock { diff --git a/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts b/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts index 9bab71987be..b88833a337e 100644 --- a/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts +++ b/public/app/plugins/panel/graph/specs/graph_ctrl.test.ts @@ -1,6 +1,7 @@ -import { GraphCtrl } from '../module'; import { dateTime } from '@grafana/data'; +import { GraphCtrl } from '../module'; + jest.mock('../graph', () => ({})); describe.skip('GraphCtrl', () => { diff --git a/public/app/plugins/panel/graph/specs/graph_tooltip.test.ts b/public/app/plugins/panel/graph/specs/graph_tooltip.test.ts index 62e4c9a5cd2..a1ceae25b25 100644 --- a/public/app/plugins/panel/graph/specs/graph_tooltip.test.ts +++ b/public/app/plugins/panel/graph/specs/graph_tooltip.test.ts @@ -1,6 +1,7 @@ jest.mock('app/core/core', () => ({})); import $ from 'jquery'; + import GraphTooltip from '../graph_tooltip'; const scope: any = { diff --git a/public/app/plugins/panel/graph/specs/threshold_manager.test.ts b/public/app/plugins/panel/graph/specs/threshold_manager.test.ts index 9f3d77a6ee3..df216a4761a 100644 --- a/public/app/plugins/panel/graph/specs/threshold_manager.test.ts +++ b/public/app/plugins/panel/graph/specs/threshold_manager.test.ts @@ -1,5 +1,7 @@ import angular from 'angular'; + import TimeSeries from 'app/core/time_series2'; + import { ThresholdManager } from '../threshold_manager'; describe('ThresholdManager', () => { diff --git a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts index 72ad92402e8..960baaa4287 100644 --- a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts +++ b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts @@ -1,6 +1,7 @@ -import { TimeRegionManager, colorModes } from '../time_region_manager'; import { dateTime } from '@grafana/data'; +import { TimeRegionManager, colorModes } from '../time_region_manager'; + describe('TimeRegionManager', () => { function plotOptionsScenario(desc: string, func: any) { describe(desc, () => { diff --git a/public/app/plugins/panel/graph/threshold_manager.ts b/public/app/plugins/panel/graph/threshold_manager.ts index f5ce394e1d5..533ef94392b 100644 --- a/public/app/plugins/panel/graph/threshold_manager.ts +++ b/public/app/plugins/panel/graph/threshold_manager.ts @@ -1,10 +1,11 @@ import 'vendor/flot/jquery.flot'; import $ from 'jquery'; import { isNumber } from 'lodash'; + import { getColorForTheme } from '@grafana/data'; -import { CoreEvents } from 'app/types'; import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; import { config } from 'app/core/config'; +import { CoreEvents } from 'app/types'; export class ThresholdManager { plot: any; diff --git a/public/app/plugins/panel/graph/thresholds_form.ts b/public/app/plugins/panel/graph/thresholds_form.ts index 5609a66b54a..bff1ea0ec75 100644 --- a/public/app/plugins/panel/graph/thresholds_form.ts +++ b/public/app/plugins/panel/graph/thresholds_form.ts @@ -1,6 +1,7 @@ +import tinycolor from 'tinycolor2'; + import coreModule from 'app/angular/core_module'; import config from 'app/core/config'; -import tinycolor from 'tinycolor2'; export class ThresholdFormCtrl { panelCtrl: any; panel: any; diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index 5d50a2ae34e..cd83a083871 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -1,5 +1,6 @@ import 'vendor/flot/jquery.flot'; import { map } from 'lodash'; + import { getColorForTheme, dateTime, DateTime, AbsoluteTimeRange, GrafanaTheme } from '@grafana/data'; import { config } from 'app/core/config'; diff --git a/public/app/plugins/panel/graph/time_regions_form.ts b/public/app/plugins/panel/graph/time_regions_form.ts index 3c911c59a8e..98eb0f1d207 100644 --- a/public/app/plugins/panel/graph/time_regions_form.ts +++ b/public/app/plugins/panel/graph/time_regions_form.ts @@ -1,4 +1,5 @@ import coreModule from 'app/angular/core_module'; + import { getColorModes } from './time_region_manager'; export class TimeRegionFormCtrl { diff --git a/public/app/plugins/panel/graph/utils.test.ts b/public/app/plugins/panel/graph/utils.test.ts index daa1f03b01b..071ecc5d132 100644 --- a/public/app/plugins/panel/graph/utils.test.ts +++ b/public/app/plugins/panel/graph/utils.test.ts @@ -1,4 +1,5 @@ import { toDataFrame, FieldType } from '@grafana/data'; + import { getDataTimeRange } from './utils'; describe('DataFrame utility functions', () => { diff --git a/public/app/plugins/panel/heatmap-new/HeatmapHoverView.tsx b/public/app/plugins/panel/heatmap-new/HeatmapHoverView.tsx index 8f83d59879f..97b5d3419bf 100644 --- a/public/app/plugins/panel/heatmap-new/HeatmapHoverView.tsx +++ b/public/app/plugins/panel/heatmap-new/HeatmapHoverView.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useRef } from 'react'; -import { Field, FieldType, formattedValueToString, getFieldDisplayName, LinkModel } from '@grafana/data'; -import { HeatmapHoverEvent } from './utils'; -import { BucketLayout, HeatmapData } from './fields'; +import { Field, FieldType, formattedValueToString, getFieldDisplayName, LinkModel } from '@grafana/data'; import { LinkButton, VerticalGroup } from '@grafana/ui'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { BucketLayout, HeatmapData } from './fields'; +import { HeatmapHoverEvent } from './utils'; + type Props = { data: HeatmapData; hover: HeatmapHoverEvent; diff --git a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx index 490f66842ff..d1cf7093238 100644 --- a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx @@ -1,6 +1,8 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; import { css } from '@emotion/css'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; + import { GrafanaTheme2, PanelProps, reduceField, ReducerID, TimeRange } from '@grafana/data'; +import { PanelDataErrorView } from '@grafana/runtime'; import { Portal, UPlotChart, @@ -10,15 +12,14 @@ import { VizTooltipContainer, LegendDisplayMode, } from '@grafana/ui'; -import { PanelDataErrorView } from '@grafana/runtime'; +import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; +import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; +import { HeatmapHoverView } from './HeatmapHoverView'; import { HeatmapData, prepareHeatmapData } from './fields'; import { PanelOptions } from './models.gen'; import { quantizeScheme } from './palettes'; import { HeatmapHoverEvent, prepConfig } from './utils'; -import { HeatmapHoverView } from './HeatmapHoverView'; -import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; -import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; interface HeatmapPanelProps extends PanelProps {} diff --git a/public/app/plugins/panel/heatmap-new/fields.test.ts b/public/app/plugins/panel/heatmap-new/fields.test.ts index a8c5e7ee18a..7bc2e1b1441 100644 --- a/public/app/plugins/panel/heatmap-new/fields.test.ts +++ b/public/app/plugins/panel/heatmap-new/fields.test.ts @@ -1,4 +1,5 @@ import { createTheme } from '@grafana/data'; + import { PanelOptions } from './models.gen'; const theme = createTheme(); diff --git a/public/app/plugins/panel/heatmap-new/fields.ts b/public/app/plugins/panel/heatmap-new/fields.ts index 66030132e22..4d120439e38 100644 --- a/public/app/plugins/panel/heatmap-new/fields.ts +++ b/public/app/plugins/panel/heatmap-new/fields.ts @@ -9,6 +9,7 @@ import { GrafanaTheme2, } from '@grafana/data'; import { calculateHeatmapFromData, bucketsToScanlines } from 'app/features/transformers/calculateHeatmap/heatmap'; + import { HeatmapSourceMode, PanelOptions } from './models.gen'; export const enum BucketLayout { diff --git a/public/app/plugins/panel/heatmap-new/migrations.test.ts b/public/app/plugins/panel/heatmap-new/migrations.test.ts index acbfbbdfc9a..43e5465d0c4 100644 --- a/public/app/plugins/panel/heatmap-new/migrations.test.ts +++ b/public/app/plugins/panel/heatmap-new/migrations.test.ts @@ -1,4 +1,5 @@ import { PanelModel, FieldConfigSource } from '@grafana/data'; + import { heatmapChangedHandler } from './migrations'; describe('Heatmap Migrations', () => { diff --git a/public/app/plugins/panel/heatmap-new/migrations.ts b/public/app/plugins/panel/heatmap-new/migrations.ts index a5c00a66abc..9daea701438 100644 --- a/public/app/plugins/panel/heatmap-new/migrations.ts +++ b/public/app/plugins/panel/heatmap-new/migrations.ts @@ -4,6 +4,7 @@ import { HeatmapCalculationMode, HeatmapCalculationOptions, } from 'app/features/transformers/calculateHeatmap/models.gen'; + import { HeatmapSourceMode, PanelOptions, defaultPanelOptions } from './models.gen'; /** diff --git a/public/app/plugins/panel/heatmap-new/module.tsx b/public/app/plugins/panel/heatmap-new/module.tsx index d425a25217d..397e1f0d1fe 100644 --- a/public/app/plugins/panel/heatmap-new/module.tsx +++ b/public/app/plugins/panel/heatmap-new/module.tsx @@ -1,8 +1,14 @@ import React from 'react'; -import { GraphFieldConfig, VisibilityMode } from '@grafana/schema'; + import { Field, FieldType, PanelPlugin } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { GraphFieldConfig, VisibilityMode } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; +import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; +import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper'; + import { HeatmapPanel } from './HeatmapPanel'; +import { heatmapChangedHandler } from './migrations'; import { PanelOptions, defaultPanelOptions, @@ -10,12 +16,8 @@ import { HeatmapColorMode, HeatmapColorScale, } from './models.gen'; -import { HeatmapSuggestionsSupplier } from './suggestions'; -import { heatmapChangedHandler } from './migrations'; -import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper'; import { colorSchemes, quantizeScheme } from './palettes'; -import { config } from '@grafana/runtime'; -import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; +import { HeatmapSuggestionsSupplier } from './suggestions'; export const plugin = new PanelPlugin(HeatmapPanel) .useFieldConfig() diff --git a/public/app/plugins/panel/heatmap-new/palettes.ts b/public/app/plugins/panel/heatmap-new/palettes.ts index 50fa0a54f9b..7a4ea65c46d 100644 --- a/public/app/plugins/panel/heatmap-new/palettes.ts +++ b/public/app/plugins/panel/heatmap-new/palettes.ts @@ -1,9 +1,11 @@ -import { GrafanaTheme2 } from '@grafana/data'; import * as d3 from 'd3'; import * as d3ScaleChromatic from 'd3-scale-chromatic'; -import { HeatmapColorOptions, defaultPanelOptions, HeatmapColorMode, HeatmapColorScale } from './models.gen'; import tinycolor from 'tinycolor2'; +import { GrafanaTheme2 } from '@grafana/data'; + +import { HeatmapColorOptions, defaultPanelOptions, HeatmapColorMode, HeatmapColorScale } from './models.gen'; + // https://observablehq.com/@d3/color-schemes?collection=@d3/d3-scale-chromatic // the previous heatmap panel used d3 deps and some code to interpolate to static 9-color palettes. here we just hard-code them for clarity. diff --git a/public/app/plugins/panel/heatmap-new/suggestions.ts b/public/app/plugins/panel/heatmap-new/suggestions.ts index 508c88b0040..70d84a51dbc 100644 --- a/public/app/plugins/panel/heatmap-new/suggestions.ts +++ b/public/app/plugins/panel/heatmap-new/suggestions.ts @@ -1,5 +1,6 @@ import { VisualizationSuggestionsBuilder } from '@grafana/data'; import { config } from '@grafana/runtime'; + import { prepareHeatmapData } from './fields'; import { PanelOptions, defaultPanelOptions } from './models.gen'; diff --git a/public/app/plugins/panel/heatmap-new/utils.ts b/public/app/plugins/panel/heatmap-new/utils.ts index 7026df72f39..11539a30693 100644 --- a/public/app/plugins/panel/heatmap-new/utils.ts +++ b/public/app/plugins/panel/heatmap-new/utils.ts @@ -1,10 +1,12 @@ import { MutableRefObject, RefObject } from 'react'; +import uPlot from 'uplot'; + import { GrafanaTheme2, TimeRange } from '@grafana/data'; import { AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/schema'; import { UPlotConfigBuilder } from '@grafana/ui'; -import uPlot from 'uplot'; import { pointWithin, Quadtree, Rect } from '../barchart/quadtree'; + import { BucketLayout, HeatmapData } from './fields'; interface PathbuilderOpts { diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index bb8c3fa99d1..d6eb9892d21 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -1,12 +1,14 @@ -import { find, isEmpty, isNil, sortBy, uniq } from 'lodash'; -import $ from 'jquery'; import * as d3 from 'd3'; +import $ from 'jquery'; +import { find, isEmpty, isNil, sortBy, uniq } from 'lodash'; + +import { PanelEvents, getColorForTheme } from '@grafana/data'; +import coreModule from 'app/angular/core_module'; +import { config } from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { tickStep } from 'app/core/utils/ticks'; + import { getColorScale, getOpacityScale } from './color_scale'; -import coreModule from 'app/angular/core_module'; -import { PanelEvents, getColorForTheme } from '@grafana/data'; -import { config } from 'app/core/config'; const LEGEND_HEIGHT_PX = 6; const LEGEND_WIDTH_PX = 100; diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index c9effe62ba9..0ad0642afbd 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -1,10 +1,20 @@ -import { MetricsPanelCtrl } from 'app/plugins/sdk'; +import { auto } from 'angular'; import { defaultsDeep, includes, keys, map, reduce, min as _min, max as _max } from 'lodash'; -import kbn from 'app/core/utils/kbn'; + +import { LegacyResponseData, PanelEvents, DataFrame, rangeUtil } from '@grafana/data'; +import appEvents from 'app/core/app_events'; import TimeSeries from 'app/core/time_series2'; +import kbn from 'app/core/utils/kbn'; +import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; +import { TemplateSrv } from 'app/features/templating/template_srv'; +import { MetricsPanelCtrl } from 'app/plugins/sdk'; +import { ZoomOutEvent } from 'app/types/events'; + +import { DataProcessor } from '../graph/data_processor'; + import { axesEditor } from './axes_editor'; import { heatmapDisplayEditor } from './display_editor'; -import rendering from './rendering'; import { convertToHeatMap, convertToCards, @@ -12,14 +22,7 @@ import { calculateBucketSize, sortSeriesByLabel, } from './heatmap_data_converter'; -import { auto } from 'angular'; -import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; -import { DataProcessor } from '../graph/data_processor'; -import { LegacyResponseData, PanelEvents, DataFrame, rangeUtil } from '@grafana/data'; -import { TemplateSrv } from 'app/features/templating/template_srv'; -import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import appEvents from 'app/core/app_events'; -import { ZoomOutEvent } from 'app/types/events'; +import rendering from './rendering'; const X_BUCKET_NUMBER_DEFAULT = 30; const Y_BUCKET_NUMBER_DEFAULT = 10; diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts index 570590adec9..95183a6a7f8 100644 --- a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts +++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts @@ -1,5 +1,7 @@ import { concat, forEach, isEmpty, isEqual, isNumber, sortBy } from 'lodash'; + import { TimeSeries } from 'app/core/core'; + import { Bucket, HeatmapCard, HeatmapCardStats, YBucket, XBucket } from './types'; const VALUE_INDEX = 0; diff --git a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts index 3c2614d5f62..e7400ee04e4 100644 --- a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts +++ b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts @@ -1,9 +1,11 @@ import * as d3 from 'd3'; import $ from 'jquery'; import { filter, find, isNumber, map, reduce } from 'lodash'; -import { getValueBucketBound } from './heatmap_data_converter'; + import { getValueFormat, formattedValueToString } from '@grafana/data'; +import { getValueBucketBound } from './heatmap_data_converter'; + const TOOLTIP_PADDING_X = 30; const TOOLTIP_PADDING_Y = 5; const HISTOGRAM_WIDTH = 160; diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 664c0baf68f..a397249286c 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -1,11 +1,7 @@ -import { find, isEmpty, isNaN, isNil, isString, map, max, min, toNumber } from 'lodash'; -import $ from 'jquery'; import * as d3 from 'd3'; -import { contextSrv } from 'app/core/core'; -import * as ticksUtils from 'app/core/utils/ticks'; -import { HeatmapTooltip } from './heatmap_tooltip'; -import { mergeZeroBuckets } from './heatmap_data_converter'; -import { getColorScale, getOpacityScale } from './color_scale'; +import $ from 'jquery'; +import { find, isEmpty, isNaN, isNil, isString, map, max, min, toNumber } from 'lodash'; + import { dateTimeFormat, formattedValueToString, @@ -18,6 +14,12 @@ import { } from '@grafana/data'; import { graphTimeFormat } from '@grafana/ui'; import { config } from 'app/core/config'; +import { contextSrv } from 'app/core/core'; +import * as ticksUtils from 'app/core/utils/ticks'; + +import { getColorScale, getOpacityScale } from './color_scale'; +import { mergeZeroBuckets } from './heatmap_data_converter'; +import { HeatmapTooltip } from './heatmap_tooltip'; const MIN_CARD_SIZE = 1, CARD_PADDING = 1, diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts b/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts index 2076cf522d9..8f30bf4c14a 100644 --- a/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts +++ b/public/app/plugins/panel/heatmap/specs/heatmap_ctrl.test.ts @@ -1,7 +1,8 @@ -import { HeatmapCtrl } from '../heatmap_ctrl'; import { dateTime } from '@grafana/data'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { HeatmapCtrl } from '../heatmap_ctrl'; + describe('HeatmapCtrl', () => { const ctx = {} as any; diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter.test.ts b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter.test.ts index d29751929a5..7fbcef1b816 100644 --- a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter.test.ts +++ b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter.test.ts @@ -1,6 +1,8 @@ import { cloneDeep, each, map } from 'lodash'; -import { describe, beforeEach, it, expect } from '../../../../../test/lib/common'; + import TimeSeries from 'app/core/time_series2'; + +import { describe, beforeEach, it, expect } from '../../../../../test/lib/common'; import { convertToHeatMap, convertToCards, diff --git a/public/app/plugins/panel/histogram/Histogram.tsx b/public/app/plugins/panel/histogram/Histogram.tsx index 29115ecff96..ae38a2804c6 100644 --- a/public/app/plugins/panel/histogram/Histogram.tsx +++ b/public/app/plugins/panel/histogram/Histogram.tsx @@ -1,5 +1,17 @@ import React from 'react'; import uPlot, { AlignedData } from 'uplot'; + +import { + DataFrame, + formattedValueToString, + getFieldColorModeForField, + getFieldSeriesColor, + GrafanaTheme2, +} from '@grafana/data'; +import { + histogramBucketSizes, + histogramFrameBucketMaxFieldName, +} from '@grafana/data/src/transformations/transformers/histogram'; import { VizLegendOptions, LegendDisplayMode, @@ -8,13 +20,6 @@ import { ScaleDirection, ScaleOrientation, } from '@grafana/schema'; -import { - DataFrame, - formattedValueToString, - getFieldColorModeForField, - getFieldSeriesColor, - GrafanaTheme2, -} from '@grafana/data'; import { Themeable2, UPlotConfigBuilder, @@ -25,10 +30,6 @@ import { UPLOT_AXIS_FONT_SIZE, } from '@grafana/ui'; -import { - histogramBucketSizes, - histogramFrameBucketMaxFieldName, -} from '@grafana/data/src/transformations/transformers/histogram'; import { PanelOptions } from './models.gen'; function incrRoundDn(num: number, incr: number) { diff --git a/public/app/plugins/panel/histogram/HistogramPanel.tsx b/public/app/plugins/panel/histogram/HistogramPanel.tsx index 71b5170dc15..dc459235915 100644 --- a/public/app/plugins/panel/histogram/HistogramPanel.tsx +++ b/public/app/plugins/panel/histogram/HistogramPanel.tsx @@ -1,14 +1,14 @@ import React, { useMemo } from 'react'; + import { PanelProps, buildHistogram, getHistogramFields } from '@grafana/data'; +import { histogramFieldsToFrame } from '@grafana/data/src/transformations/transformers/histogram'; +import { useTheme2 } from '@grafana/ui'; import { Histogram, getBucketSize } from './Histogram'; import { PanelOptions } from './models.gen'; -import { useTheme2 } from '@grafana/ui'; type Props = PanelProps; -import { histogramFieldsToFrame } from '@grafana/data/src/transformations/transformers/histogram'; - export const HistogramPanel: React.FC = ({ data, options, width, height }) => { const theme = useTheme2(); diff --git a/public/app/plugins/panel/histogram/module.tsx b/public/app/plugins/panel/histogram/module.tsx index 8eef08ec341..de820bb09ee 100644 --- a/public/app/plugins/panel/histogram/module.tsx +++ b/public/app/plugins/panel/histogram/module.tsx @@ -1,11 +1,11 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data'; -import { HistogramPanel } from './HistogramPanel'; +import { histogramFieldInfo } from '@grafana/data/src/transformations/transformers/histogram'; import { commonOptionsBuilder, graphFieldOptions } from '@grafana/ui'; + +import { HistogramPanel } from './HistogramPanel'; import { PanelFieldConfig, PanelOptions, defaultPanelFieldConfig, defaultPanelOptions } from './models.gen'; import { originalDataHasHistogram } from './utils'; -import { histogramFieldInfo } from '@grafana/data/src/transformations/transformers/histogram'; - export const plugin = new PanelPlugin(HistogramPanel) .setPanelOptions((builder) => { builder diff --git a/public/app/plugins/panel/histogram/utils.ts b/public/app/plugins/panel/histogram/utils.ts index 5f6de56ab79..a98e8768f3d 100644 --- a/public/app/plugins/panel/histogram/utils.ts +++ b/public/app/plugins/panel/histogram/utils.ts @@ -1,5 +1,4 @@ import { DataFrame, FieldType } from '@grafana/data'; - import { histogramFrameBucketMinFieldName, histogramFrameBucketMaxFieldName, diff --git a/public/app/plugins/panel/icon/IconPanel.tsx b/public/app/plugins/panel/icon/IconPanel.tsx index 41f244db6b8..2c679db7084 100644 --- a/public/app/plugins/panel/icon/IconPanel.tsx +++ b/public/app/plugins/panel/icon/IconPanel.tsx @@ -1,8 +1,9 @@ import React, { Component } from 'react'; + import { PanelProps } from '@grafana/data'; -import { PanelOptions } from './models.gen'; -import { ElementState } from 'app/features/canvas/runtime/element'; +import { HorizontalConstraint, VerticalConstraint } from 'app/features/canvas'; import { iconItem } from 'app/features/canvas/elements/icon'; +import { ElementState } from 'app/features/canvas/runtime/element'; import { ColorDimensionConfig, DimensionContext, @@ -17,6 +18,8 @@ import { TextDimensionConfig, } from 'app/features/dimensions'; +import { PanelOptions } from './models.gen'; + interface Props extends PanelProps {} export class IconPanel extends Component { @@ -36,17 +39,16 @@ export class IconPanel extends Component { updateSize = (props: Props) => { const { width, height } = props; - this.element.anchor = { - top: true, - left: true, + this.element.options.constraint = { + vertical: VerticalConstraint.Top, + horizontal: HorizontalConstraint.Left, }; - this.element.placement = { + this.element.options.placement = { left: 0, top: 0, width, height, }; - this.element.updateSize(width, height); }; dims: DimensionContext = { diff --git a/public/app/plugins/panel/icon/module.tsx b/public/app/plugins/panel/icon/module.tsx index 465dde0e3da..37e8706b0a5 100644 --- a/public/app/plugins/panel/icon/module.tsx +++ b/public/app/plugins/panel/icon/module.tsx @@ -1,10 +1,11 @@ import { FieldConfigProperty, PanelPlugin } from '@grafana/data'; +import { CanvasElementOptions } from 'app/features/canvas'; +import { IconConfig, iconItem } from 'app/features/canvas/elements/icon'; + +import { optionBuilder } from '../canvas/editor/options'; import { IconPanel } from './IconPanel'; import { defaultPanelOptions, PanelOptions } from './models.gen'; -import { IconConfig, iconItem } from 'app/features/canvas/elements/icon'; -import { optionBuilder } from '../canvas/editor/options'; -import { CanvasElementOptions } from 'app/features/canvas'; export const plugin = new PanelPlugin(IconPanel) .setNoPadding() // extend to panel edges diff --git a/public/app/plugins/panel/live/LiveChannelEditor.tsx b/public/app/plugins/panel/live/LiveChannelEditor.tsx index 3f475e1d9b2..2f61b2834f1 100644 --- a/public/app/plugins/panel/live/LiveChannelEditor.tsx +++ b/public/app/plugins/panel/live/LiveChannelEditor.tsx @@ -1,6 +1,6 @@ -import React, { PureComponent } from 'react'; import { css } from '@emotion/css'; -import { Select, Alert, Label, stylesFactory } from '@grafana/ui'; +import React, { PureComponent } from 'react'; + import { LiveChannelScope, LiveChannelAddress, @@ -8,9 +8,10 @@ import { StandardEditorProps, GrafanaTheme, } from '@grafana/data'; +import { Select, Alert, Label, stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; import { LivePanelOptions } from './types'; -import { config } from 'app/core/config'; type Props = StandardEditorProps; diff --git a/public/app/plugins/panel/live/LivePanel.tsx b/public/app/plugins/panel/live/LivePanel.tsx index b84332f7729..e9c2aebb0ab 100755 --- a/public/app/plugins/panel/live/LivePanel.tsx +++ b/public/app/plugins/panel/live/LivePanel.tsx @@ -1,6 +1,8 @@ +import { css, cx } from '@emotion/css'; +import { isEqual } from 'lodash'; import React, { PureComponent } from 'react'; import { Unsubscribable, PartialObserver } from 'rxjs'; -import { Alert, stylesFactory, Button, JSONFormatter, CustomScrollbar, CodeEditor } from '@grafana/ui'; + import { GrafanaTheme, PanelProps, @@ -15,13 +17,14 @@ import { applyFieldOverrides, LiveChannelAddress, } from '@grafana/data'; -import { TablePanel } from '../table/TablePanel'; -import { LivePanelOptions, MessageDisplayMode } from './types'; import { config, getGrafanaLiveSrv } from '@grafana/runtime'; -import { css, cx } from '@emotion/css'; -import { isEqual } from 'lodash'; +import { Alert, stylesFactory, Button, JSONFormatter, CustomScrollbar, CodeEditor } from '@grafana/ui'; import { StreamingDataFrame } from 'app/features/live/data/StreamingDataFrame'; +import { TablePanel } from '../table/TablePanel'; + +import { LivePanelOptions, MessageDisplayMode } from './types'; + interface Props extends PanelProps {} interface State { diff --git a/public/app/plugins/panel/live/module.tsx b/public/app/plugins/panel/live/module.tsx index 113b126cb14..596083f450c 100755 --- a/public/app/plugins/panel/live/module.tsx +++ b/public/app/plugins/panel/live/module.tsx @@ -1,4 +1,5 @@ import { PanelPlugin } from '@grafana/data'; + import { LiveChannelEditor } from './LiveChannelEditor'; import { LivePanel } from './LivePanel'; import { LivePanelOptions, MessageDisplayMode } from './types'; diff --git a/public/app/plugins/panel/logs/LogsPanel.test.tsx b/public/app/plugins/panel/logs/LogsPanel.test.tsx index 048773a294a..99ad2026a78 100644 --- a/public/app/plugins/panel/logs/LogsPanel.test.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.test.tsx @@ -1,6 +1,8 @@ -import React, { ComponentProps } from 'react'; import { render, screen } from '@testing-library/react'; +import React, { ComponentProps } from 'react'; + import { LoadingState, MutableDataFrame, FieldType, LogsSortOrder } from '@grafana/data'; + import { LogsPanel } from './LogsPanel'; type LogsPanelProps = ComponentProps; diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 72f742ab929..f71bc05bdeb 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { LogRows, CustomScrollbar, LogLabels, useStyles2, usePanelContext } from '@grafana/ui'; +import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react'; + import { PanelProps, Field, @@ -11,12 +11,15 @@ import { DataHoverClearEvent, DataHoverEvent, } from '@grafana/data'; -import { Options } from './types'; +import { LogRows, CustomScrollbar, LogLabels, useStyles2, usePanelContext } from '@grafana/ui'; import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model'; import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; -import { COMMON_LABELS } from '../../../core/logs_model'; import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView'; +import { COMMON_LABELS } from '../../../core/logs_model'; + +import { Options } from './types'; + interface LogsPanelProps extends PanelProps {} export const LogsPanel: React.FunctionComponent = ({ diff --git a/public/app/plugins/panel/logs/module.tsx b/public/app/plugins/panel/logs/module.tsx index 5681d7394b3..09df585c322 100644 --- a/public/app/plugins/panel/logs/module.tsx +++ b/public/app/plugins/panel/logs/module.tsx @@ -1,7 +1,8 @@ import { PanelPlugin, LogsSortOrder, LogsDedupStrategy, LogsDedupDescription } from '@grafana/data'; -import { Options } from './types'; + import { LogsPanel } from './LogsPanel'; import { LogsPanelSuggestionsSupplier } from './suggestions'; +import { Options } from './types'; export const plugin = new PanelPlugin(LogsPanel) .setPanelOptions((builder) => { diff --git a/public/app/plugins/panel/logs/suggestions.ts b/public/app/plugins/panel/logs/suggestions.ts index 5a0562a4296..6f7daa57c05 100644 --- a/public/app/plugins/panel/logs/suggestions.ts +++ b/public/app/plugins/panel/logs/suggestions.ts @@ -1,5 +1,6 @@ import { VisualizationSuggestionsBuilder, VisualizationSuggestionScore } from '@grafana/data'; import { SuggestionName } from 'app/types/suggestions'; + import { Options } from './types'; export class LogsPanelSuggestionsSupplier { diff --git a/public/app/plugins/panel/news/NewsPanel.tsx b/public/app/plugins/panel/news/NewsPanel.tsx index e75390bb65a..7e3a58ff365 100755 --- a/public/app/plugins/panel/news/NewsPanel.tsx +++ b/public/app/plugins/panel/news/NewsPanel.tsx @@ -1,22 +1,18 @@ -// Libraries -import React, { PureComponent } from 'react'; - -// Utils & Services -import { CustomScrollbar, stylesFactory } from '@grafana/ui'; - -import config from 'app/core/config'; -import { feedToDataFrame } from './utils'; -import { loadFeed } from './feed'; - -// Types -import { PanelProps, DataFrameView, dateTimeFormat, GrafanaTheme2, textUtil } from '@grafana/data'; -import { NewsItem } from './types'; -import { PanelOptions } from './models.gen'; -import { DEFAULT_FEED_URL } from './constants'; import { css, cx } from '@emotion/css'; -import { RefreshEvent } from '@grafana/runtime'; +import React, { PureComponent } from 'react'; import { Unsubscribable } from 'rxjs'; +import { PanelProps, DataFrameView, dateTimeFormat, GrafanaTheme2, textUtil } from '@grafana/data'; +import { RefreshEvent } from '@grafana/runtime'; +import { CustomScrollbar, stylesFactory } from '@grafana/ui'; +import config from 'app/core/config'; + +import { DEFAULT_FEED_URL } from './constants'; +import { loadFeed } from './feed'; +import { PanelOptions } from './models.gen'; +import { NewsItem } from './types'; +import { feedToDataFrame } from './utils'; + interface Props extends PanelProps {} interface State { diff --git a/public/app/plugins/panel/news/atom.test.ts b/public/app/plugins/panel/news/atom.test.ts index 731c597187d..57d5a1a93bc 100644 --- a/public/app/plugins/panel/news/atom.test.ts +++ b/public/app/plugins/panel/news/atom.test.ts @@ -1,6 +1,7 @@ -import { parseAtomFeed } from './atom'; import fs from 'fs'; +import { parseAtomFeed } from './atom'; + describe('Atom feed parser', () => { it('should successfully parse an atom feed', async () => { const atomFile = fs.readFileSync(`${__dirname}/fixtures/atom.xml`, 'utf8'); diff --git a/public/app/plugins/panel/news/module.tsx b/public/app/plugins/panel/news/module.tsx index 382cddb2c7b..13c2448b1ca 100755 --- a/public/app/plugins/panel/news/module.tsx +++ b/public/app/plugins/panel/news/module.tsx @@ -1,7 +1,8 @@ import { PanelPlugin } from '@grafana/data'; + import { NewsPanel } from './NewsPanel'; -import { PanelOptions, defaultPanelOptions } from './models.gen'; import { DEFAULT_FEED_URL } from './constants'; +import { PanelOptions, defaultPanelOptions } from './models.gen'; export const plugin = new PanelPlugin(NewsPanel).setPanelOptions((builder) => { builder diff --git a/public/app/plugins/panel/news/rss.test.ts b/public/app/plugins/panel/news/rss.test.ts index 5c30bcc0e00..d4cbbd80a1a 100644 --- a/public/app/plugins/panel/news/rss.test.ts +++ b/public/app/plugins/panel/news/rss.test.ts @@ -1,6 +1,7 @@ -import { parseRSSFeed } from './rss'; import fs from 'fs'; +import { parseRSSFeed } from './rss'; + describe('RSS feed parser', () => { it('should successfully parse an rss feed', async () => { const rssFile = fs.readFileSync(`${__dirname}/fixtures/rss.xml`, 'utf8'); diff --git a/public/app/plugins/panel/news/utils.test.ts b/public/app/plugins/panel/news/utils.test.ts index fee1cbf2ffb..b0424446c42 100644 --- a/public/app/plugins/panel/news/utils.test.ts +++ b/public/app/plugins/panel/news/utils.test.ts @@ -1,7 +1,8 @@ -import { feedToDataFrame } from './utils'; -import { Feed, NewsItem } from './types'; import { DataFrameView } from '@grafana/data'; +import { Feed, NewsItem } from './types'; +import { feedToDataFrame } from './utils'; + describe('news', () => { test('convert RssFeed to DataFrame', () => { const frame = feedToDataFrame(grafana20191216); diff --git a/public/app/plugins/panel/news/utils.ts b/public/app/plugins/panel/news/utils.ts index 1b737221548..f36d7a48cb1 100644 --- a/public/app/plugins/panel/news/utils.ts +++ b/public/app/plugins/panel/news/utils.ts @@ -1,6 +1,7 @@ -import { Feed } from './types'; import { ArrayVector, FieldType, DataFrame, dateTime } from '@grafana/data'; +import { Feed } from './types'; + export function feedToDataFrame(feed: Feed): DataFrame { const date = new ArrayVector([]); const title = new ArrayVector([]); diff --git a/public/app/plugins/panel/nodeGraph/Edge.tsx b/public/app/plugins/panel/nodeGraph/Edge.tsx index 9e8c3ebd51d..e6b8ced56dc 100644 --- a/public/app/plugins/panel/nodeGraph/Edge.tsx +++ b/public/app/plugins/panel/nodeGraph/Edge.tsx @@ -1,4 +1,5 @@ import React, { MouseEvent, memo } from 'react'; + import { EdgeDatum, NodeDatum } from './types'; import { shortenLine } from './utils'; diff --git a/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx b/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx index e97f56f4754..03cebc01343 100644 --- a/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx +++ b/public/app/plugins/panel/nodeGraph/EdgeLabel.tsx @@ -1,8 +1,10 @@ -import React, { memo } from 'react'; -import { EdgeDatum, NodeDatum } from './types'; import { css } from '@emotion/css'; +import React, { memo } from 'react'; + import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; + +import { EdgeDatum, NodeDatum } from './types'; import { shortenLine } from './utils'; const getStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/plugins/panel/nodeGraph/Legend.test.tsx b/public/app/plugins/panel/nodeGraph/Legend.test.tsx index 2fe9fddeb94..fb9ac756ee5 100644 --- a/public/app/plugins/panel/nodeGraph/Legend.test.tsx +++ b/public/app/plugins/panel/nodeGraph/Legend.test.tsx @@ -1,6 +1,8 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; +import React from 'react'; + import { FieldColorModeId } from '@grafana/data'; + import { Legend } from './Legend'; import { NodeDatum } from './types'; diff --git a/public/app/plugins/panel/nodeGraph/Legend.tsx b/public/app/plugins/panel/nodeGraph/Legend.tsx index a46a12b51d7..f2f7184b16e 100644 --- a/public/app/plugins/panel/nodeGraph/Legend.tsx +++ b/public/app/plugins/panel/nodeGraph/Legend.tsx @@ -1,12 +1,14 @@ -import React, { useCallback } from 'react'; -import { NodeDatum } from './types'; -import { Field, FieldColorModeId, getColorForTheme, GrafanaTheme } from '@grafana/data'; -import { identity } from 'lodash'; -import { Config } from './layout'; import { css } from '@emotion/css'; +import { identity } from 'lodash'; +import React, { useCallback } from 'react'; + +import { Field, FieldColorModeId, getColorForTheme, GrafanaTheme } from '@grafana/data'; import { LegendDisplayMode } from '@grafana/schema'; import { Icon, useStyles, useTheme, VizLegend, VizLegendItem, VizLegendListItem } from '@grafana/ui'; +import { Config } from './layout'; +import { NodeDatum } from './types'; + function getStyles() { return { item: css` diff --git a/public/app/plugins/panel/nodeGraph/Marker.tsx b/public/app/plugins/panel/nodeGraph/Marker.tsx index 89ef5c9512f..8f73d3912a1 100644 --- a/public/app/plugins/panel/nodeGraph/Marker.tsx +++ b/public/app/plugins/panel/nodeGraph/Marker.tsx @@ -1,9 +1,11 @@ -import React, { MouseEvent, memo } from 'react'; -import { NodesMarker } from './types'; -import { GrafanaTheme } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { MouseEvent, memo } from 'react'; + +import { GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme } from '@grafana/ui'; +import { NodesMarker } from './types'; + const nodeR = 40; const getStyles = stylesFactory((theme: GrafanaTheme) => ({ diff --git a/public/app/plugins/panel/nodeGraph/Node.tsx b/public/app/plugins/panel/nodeGraph/Node.tsx index 44772bad586..61aae2373fc 100644 --- a/public/app/plugins/panel/nodeGraph/Node.tsx +++ b/public/app/plugins/panel/nodeGraph/Node.tsx @@ -1,10 +1,12 @@ -import React, { MouseEvent, memo } from 'react'; +import { css } from '@emotion/css'; import cx from 'classnames'; +import React, { MouseEvent, memo } from 'react'; +import tinycolor from 'tinycolor2'; + import { Field, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '@grafana/ui'; + import { NodeDatum } from './types'; -import { css } from '@emotion/css'; -import tinycolor from 'tinycolor2'; import { statToString } from './utils'; const nodeR = 40; diff --git a/public/app/plugins/panel/nodeGraph/NodeGraph.test.tsx b/public/app/plugins/panel/nodeGraph/NodeGraph.test.tsx index e6bdf8380c7..55f907a4492 100644 --- a/public/app/plugins/panel/nodeGraph/NodeGraph.test.tsx +++ b/public/app/plugins/panel/nodeGraph/NodeGraph.test.tsx @@ -1,9 +1,9 @@ -import React from 'react'; import { render, screen, fireEvent, waitFor, getByText } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; + import { NodeGraph } from './NodeGraph'; import { makeEdgesDataFrame, makeNodesDataFrame } from './utils'; -import { act } from 'react-dom/test-utils'; jest.mock('react-use/lib/useMeasure', () => { return { @@ -32,9 +32,9 @@ describe('NodeGraph', () => { const zoomOut = await screen.findByTitle(/Zoom out/); expect(getScale()).toBe(1); - userEvent.click(zoomIn); + await userEvent.click(zoomIn); expect(getScale()).toBe(1.5); - userEvent.click(zoomOut); + await userEvent.click(zoomOut); expect(getScale()).toBe(1); }); @@ -84,17 +84,11 @@ describe('NodeGraph', () => { /> ); const node = await screen.findByLabelText(/Node: service:0/); - // This shows warning because there is no position for the click. We cannot add any because we use pageX/Y in the - // context menu which is experimental (but supported) property and userEvents does not seem to support that - act(() => { - userEvent.click(node); - }); + await userEvent.click(node); await screen.findByText(/Node traces/); const edge = await screen.findByLabelText(/Edge from/); - act(() => { - userEvent.click(edge); - }); + await userEvent.click(edge); await screen.findByText(/Edge traces/); }); @@ -183,9 +177,7 @@ describe('NodeGraph', () => { expect(node).toBeInTheDocument(); const marker = await screen.findByLabelText(/Hidden nodes marker: 3/); - act(() => { - userEvent.click(marker); - }); + await userEvent.click(marker); expect(screen.queryByLabelText(/Node: service:0/)).not.toBeInTheDocument(); expect(screen.getByLabelText(/Node: service:4/)).toBeInTheDocument(); @@ -210,7 +202,7 @@ describe('NodeGraph', () => { ); const button = await screen.findByTitle(/Grid layout/); - userEvent.click(button); + await userEvent.click(button); await expectNodePositionCloseTo('service:0', { x: -60, y: -60 }); await expectNodePositionCloseTo('service:1', { x: 60, y: -60 }); diff --git a/public/app/plugins/panel/nodeGraph/NodeGraph.tsx b/public/app/plugins/panel/nodeGraph/NodeGraph.tsx index ad026f468aa..0c851b95a76 100644 --- a/public/app/plugins/panel/nodeGraph/NodeGraph.tsx +++ b/public/app/plugins/panel/nodeGraph/NodeGraph.tsx @@ -1,25 +1,27 @@ -import React, { memo, MouseEvent, MutableRefObject, useCallback, useMemo, useState } from 'react'; -import cx from 'classnames'; -import useMeasure from 'react-use/lib/useMeasure'; -import { Icon, Spinner, useStyles2, useTheme2 } from '@grafana/ui'; -import { usePanning } from './usePanning'; -import { EdgeDatum, NodeDatum, NodesMarker } from './types'; -import { Node } from './Node'; -import { Edge } from './Edge'; -import { ViewControls } from './ViewControls'; -import { DataFrame, GrafanaTheme2, LinkModel } from '@grafana/data'; -import { useZoom } from './useZoom'; -import { Config, defaultConfig, useLayout } from './layout'; -import { EdgeArrowMarker } from './EdgeArrowMarker'; import { css } from '@emotion/css'; -import { useCategorizeFrames } from './useCategorizeFrames'; +import cx from 'classnames'; +import React, { memo, MouseEvent, MutableRefObject, useCallback, useMemo, useState } from 'react'; +import useMeasure from 'react-use/lib/useMeasure'; + +import { DataFrame, GrafanaTheme2, LinkModel } from '@grafana/data'; +import { Icon, Spinner, useStyles2, useTheme2 } from '@grafana/ui'; + +import { Edge } from './Edge'; +import { EdgeArrowMarker } from './EdgeArrowMarker'; import { EdgeLabel } from './EdgeLabel'; -import { useContextMenu } from './useContextMenu'; -import { processNodes, Bounds } from './utils'; -import { Marker } from './Marker'; import { Legend } from './Legend'; -import { useHighlight } from './useHighlight'; +import { Marker } from './Marker'; +import { Node } from './Node'; +import { ViewControls } from './ViewControls'; +import { Config, defaultConfig, useLayout } from './layout'; +import { EdgeDatum, NodeDatum, NodesMarker } from './types'; +import { useCategorizeFrames } from './useCategorizeFrames'; +import { useContextMenu } from './useContextMenu'; import { useFocusPositionOnLayout } from './useFocusPositionOnLayout'; +import { useHighlight } from './useHighlight'; +import { usePanning } from './usePanning'; +import { useZoom } from './useZoom'; +import { processNodes, Bounds } from './utils'; const getStyles = (theme: GrafanaTheme2) => ({ wrapper: css` diff --git a/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx b/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx index 5e1394a5b2f..d98d0597789 100644 --- a/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx +++ b/public/app/plugins/panel/nodeGraph/NodeGraphPanel.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import { PanelProps } from '@grafana/data'; import memoizeOne from 'memoize-one'; -import { Options } from './types'; -import { NodeGraph } from './NodeGraph'; -import { getNodeGraphDataFrames } from './utils'; +import React from 'react'; + +import { PanelProps } from '@grafana/data'; + import { useLinks } from '../../../features/explore/utils/links'; +import { NodeGraph } from './NodeGraph'; +import { Options } from './types'; +import { getNodeGraphDataFrames } from './utils'; + export const NodeGraphPanel: React.FunctionComponent> = ({ width, height, data }) => { const getLinks = useLinks(data.timeRange); if (!data || !data.series.length) { diff --git a/public/app/plugins/panel/nodeGraph/ViewControls.tsx b/public/app/plugins/panel/nodeGraph/ViewControls.tsx index abcd9484304..1b3a7316014 100644 --- a/public/app/plugins/panel/nodeGraph/ViewControls.tsx +++ b/public/app/plugins/panel/nodeGraph/ViewControls.tsx @@ -1,6 +1,7 @@ -import React, { useState } from 'react'; -import { Button, HorizontalGroup, useStyles, VerticalGroup } from '@grafana/ui'; import { css } from '@emotion/css'; +import React, { useState } from 'react'; + +import { Button, HorizontalGroup, useStyles, VerticalGroup } from '@grafana/ui'; function getStyles() { return { diff --git a/public/app/plugins/panel/nodeGraph/layout.test.ts b/public/app/plugins/panel/nodeGraph/layout.test.ts index 4783832bfad..0c45d4b3a04 100644 --- a/public/app/plugins/panel/nodeGraph/layout.test.ts +++ b/public/app/plugins/panel/nodeGraph/layout.test.ts @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; + import { useLayout } from './layout'; import { EdgeDatum, NodeDatum } from './types'; diff --git a/public/app/plugins/panel/nodeGraph/layout.ts b/public/app/plugins/panel/nodeGraph/layout.ts index 688a31b6a1e..30e4b57a6d7 100644 --- a/public/app/plugins/panel/nodeGraph/layout.ts +++ b/public/app/plugins/panel/nodeGraph/layout.ts @@ -1,11 +1,13 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { EdgeDatum, EdgeDatumLayout, NodeDatum } from './types'; -import { Field } from '@grafana/data'; -import { useNodeLimit } from './useNodeLimit'; -import useMountedState from 'react-use/lib/useMountedState'; -import { graphBounds } from './utils'; -import { createWorker } from './createLayoutWorker'; import { useUnmount } from 'react-use'; +import useMountedState from 'react-use/lib/useMountedState'; + +import { Field } from '@grafana/data'; + +import { createWorker } from './createLayoutWorker'; +import { EdgeDatum, EdgeDatumLayout, NodeDatum } from './types'; +import { useNodeLimit } from './useNodeLimit'; +import { graphBounds } from './utils'; export interface Config { linkDistance: number; diff --git a/public/app/plugins/panel/nodeGraph/module.tsx b/public/app/plugins/panel/nodeGraph/module.tsx index 60aec529aac..15416636639 100644 --- a/public/app/plugins/panel/nodeGraph/module.tsx +++ b/public/app/plugins/panel/nodeGraph/module.tsx @@ -1,4 +1,5 @@ import { PanelPlugin } from '@grafana/data'; + import { NodeGraphPanel } from './NodeGraphPanel'; import { Options } from './types'; diff --git a/public/app/plugins/panel/nodeGraph/types.ts b/public/app/plugins/panel/nodeGraph/types.ts index 91b4103afee..1c15b9e5688 100644 --- a/public/app/plugins/panel/nodeGraph/types.ts +++ b/public/app/plugins/panel/nodeGraph/types.ts @@ -1,4 +1,5 @@ import { SimulationNodeDatum, SimulationLinkDatum } from 'd3-force'; + import { Field } from '@grafana/data'; export interface Options {} diff --git a/public/app/plugins/panel/nodeGraph/useCategorizeFrames.ts b/public/app/plugins/panel/nodeGraph/useCategorizeFrames.ts index 3e37efd6661..28cf65ca32e 100644 --- a/public/app/plugins/panel/nodeGraph/useCategorizeFrames.ts +++ b/public/app/plugins/panel/nodeGraph/useCategorizeFrames.ts @@ -1,4 +1,5 @@ import { useMemo } from 'react'; + import { DataFrame } from '@grafana/data'; /** diff --git a/public/app/plugins/panel/nodeGraph/useContextMenu.tsx b/public/app/plugins/panel/nodeGraph/useContextMenu.tsx index c3c55b0407b..3df2c7d37ed 100644 --- a/public/app/plugins/panel/nodeGraph/useContextMenu.tsx +++ b/public/app/plugins/panel/nodeGraph/useContextMenu.tsx @@ -1,11 +1,13 @@ -import React, { MouseEvent, useCallback, useState } from 'react'; -import { EdgeDatum, NodeDatum } from './types'; -import { DataFrame, Field, GrafanaTheme, LinkModel } from '@grafana/data'; -import { getEdgeFields, getNodeFields } from './utils'; import { css } from '@emotion/css'; -import { Config } from './layout'; +import React, { MouseEvent, useCallback, useState } from 'react'; + +import { DataFrame, Field, GrafanaTheme, LinkModel } from '@grafana/data'; import { ContextMenu, MenuGroup, MenuItem, stylesFactory, useTheme } from '@grafana/ui'; +import { Config } from './layout'; +import { EdgeDatum, NodeDatum } from './types'; +import { getEdgeFields, getNodeFields } from './utils'; + /** * Hook that contains state of the context menu, both for edges and nodes and provides appropriate component when * opened context menu should be opened. diff --git a/public/app/plugins/panel/nodeGraph/useFocusPositionOnLayout.ts b/public/app/plugins/panel/nodeGraph/useFocusPositionOnLayout.ts index 22da763b92e..807cbe06467 100644 --- a/public/app/plugins/panel/nodeGraph/useFocusPositionOnLayout.ts +++ b/public/app/plugins/panel/nodeGraph/useFocusPositionOnLayout.ts @@ -1,4 +1,5 @@ import usePrevious from 'react-use/lib/usePrevious'; + import { Config } from './layout'; import { NodeDatum } from './types'; diff --git a/public/app/plugins/panel/nodeGraph/useNodeLimit.ts b/public/app/plugins/panel/nodeGraph/useNodeLimit.ts index 6f0cbb07680..ab39b303099 100644 --- a/public/app/plugins/panel/nodeGraph/useNodeLimit.ts +++ b/public/app/plugins/panel/nodeGraph/useNodeLimit.ts @@ -1,7 +1,8 @@ import { fromPairs, uniq } from 'lodash'; import { useMemo } from 'react'; -import { EdgeDatumLayout, NodeDatum, NodesMarker } from './types'; + import { Config } from './layout'; +import { EdgeDatumLayout, NodeDatum, NodesMarker } from './types'; type NodesMap = Record; type EdgesMap = Record; diff --git a/public/app/plugins/panel/nodeGraph/usePanning.ts b/public/app/plugins/panel/nodeGraph/usePanning.ts index e83ff2bdd86..77563e5638d 100644 --- a/public/app/plugins/panel/nodeGraph/usePanning.ts +++ b/public/app/plugins/panel/nodeGraph/usePanning.ts @@ -1,8 +1,9 @@ import { useEffect, useRef, RefObject, useState, useMemo } from 'react'; import useMountedState from 'react-use/lib/useMountedState'; -import { Bounds } from './utils'; import usePrevious from 'react-use/lib/usePrevious'; +import { Bounds } from './utils'; + export interface State { isPanning: boolean; position: { diff --git a/public/app/plugins/panel/nodeGraph/utils.test.ts b/public/app/plugins/panel/nodeGraph/utils.test.ts index d95e4e72fdd..9ca99d86ac5 100644 --- a/public/app/plugins/panel/nodeGraph/utils.test.ts +++ b/public/app/plugins/panel/nodeGraph/utils.test.ts @@ -1,4 +1,5 @@ import { ArrayVector, createTheme, DataFrame, FieldType, MutableDataFrame } from '@grafana/data'; + import { getEdgeFields, getNodeFields, diff --git a/public/app/plugins/panel/nodeGraph/utils.ts b/public/app/plugins/panel/nodeGraph/utils.ts index 0aac55cedf8..2de6eb997ef 100644 --- a/public/app/plugins/panel/nodeGraph/utils.ts +++ b/public/app/plugins/panel/nodeGraph/utils.ts @@ -8,6 +8,7 @@ import { MutableDataFrame, NodeGraphDataFrameFieldNames, } from '@grafana/data'; + import { EdgeDatum, NodeDatum } from './types'; type Line = { x1: number; y1: number; x2: number; y2: number }; diff --git a/public/app/plugins/panel/piechart/PieChart.tsx b/public/app/plugins/panel/piechart/PieChart.tsx index f264034a7bd..70c2adeb175 100644 --- a/public/app/plugins/panel/piechart/PieChart.tsx +++ b/public/app/plugins/panel/piechart/PieChart.tsx @@ -1,5 +1,13 @@ +import { css } from '@emotion/css'; +import { localPoint } from '@visx/event'; +import { RadialGradient } from '@visx/gradient'; +import { Group } from '@visx/group'; +import Pie, { PieArcDatum, ProvidedProps } from '@visx/shape/lib/shapes/Pie'; +import { useTooltip, useTooltipInPortal } from '@visx/tooltip'; +import { UseTooltipParams } from '@visx/tooltip/lib/hooks/useTooltip'; import React, { FC, useCallback } from 'react'; -import { VizTooltipOptions } from '@grafana/schema'; +import tinycolor from 'tinycolor2'; + import { FieldDisplay, FALLBACK_COLOR, @@ -8,6 +16,8 @@ import { DataHoverClearEvent, DataHoverEvent, } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { VizTooltipOptions } from '@grafana/schema'; import { useTheme2, useStyles2, @@ -16,19 +26,10 @@ import { SeriesTable, usePanelContext, } from '@grafana/ui'; -import { PieChartType, PieChartLabels } from './types'; -import { useTooltip, useTooltipInPortal } from '@visx/tooltip'; -import Pie, { PieArcDatum, ProvidedProps } from '@visx/shape/lib/shapes/Pie'; -import { UseTooltipParams } from '@visx/tooltip/lib/hooks/useTooltip'; -import { RadialGradient } from '@visx/gradient'; -import { localPoint } from '@visx/event'; -import { Group } from '@visx/group'; -import tinycolor from 'tinycolor2'; -import { css } from '@emotion/css'; - -import { useComponentInstanceId } from '@grafana/ui/src/utils/useComponetInstanceId'; import { getTooltipContainerStyles } from '@grafana/ui/src/themes/mixins'; -import { selectors } from '@grafana/e2e-selectors'; +import { useComponentInstanceId } from '@grafana/ui/src/utils/useComponetInstanceId'; + +import { PieChartType, PieChartLabels } from './types'; import { filterDisplayItems, sumDisplayItemsReducer } from './utils'; /** diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index d8eeeb59efd..c5dbd94bf75 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { LegendDisplayMode } from '@grafana/schema'; +import { Subscription } from 'rxjs'; + import { DataHoverClearEvent, DataHoverEvent, @@ -9,11 +10,8 @@ import { getFieldDisplayValues, PanelProps, } from '@grafana/data'; - -import { PieChart } from './PieChart'; - -import { PieChartLegendOptions, PieChartLegendValues, PieChartOptions } from './types'; -import { Subscription } from 'rxjs'; +import { PanelDataErrorView } from '@grafana/runtime'; +import { LegendDisplayMode } from '@grafana/schema'; import { SeriesVisibilityChangeBehavior, usePanelContext, @@ -22,8 +20,10 @@ import { VizLegend, VizLegendItem, } from '@grafana/ui'; + +import { PieChart } from './PieChart'; +import { PieChartLegendOptions, PieChartLegendValues, PieChartOptions } from './types'; import { filterDisplayItems, sumDisplayItemsReducer } from './utils'; -import { PanelDataErrorView } from '@grafana/runtime'; const defaultLegendOptions: PieChartLegendOptions = { displayMode: LegendDisplayMode.List, diff --git a/public/app/plugins/panel/piechart/migrations.test.ts b/public/app/plugins/panel/piechart/migrations.test.ts index 1d63e0b4555..b11c492658d 100644 --- a/public/app/plugins/panel/piechart/migrations.test.ts +++ b/public/app/plugins/panel/piechart/migrations.test.ts @@ -1,5 +1,6 @@ import { FieldColorModeId, FieldConfigProperty, FieldMatcherID, PanelModel } from '@grafana/data'; import { LegendDisplayMode } from '@grafana/schema'; + import { PieChartPanelChangedHandler } from './migrations'; import { PieChartLabels } from './types'; diff --git a/public/app/plugins/panel/piechart/migrations.ts b/public/app/plugins/panel/piechart/migrations.ts index 1d12c83b6a1..fa42a9cb766 100644 --- a/public/app/plugins/panel/piechart/migrations.ts +++ b/public/app/plugins/panel/piechart/migrations.ts @@ -1,5 +1,6 @@ import { FieldColorModeId, FieldConfigProperty, FieldMatcherID, PanelModel } from '@grafana/data'; import { LegendDisplayMode } from '@grafana/schema'; + import { PieChartOptions, PieChartLabels, PieChartLegendValues, PieChartType } from './types'; export const PieChartPanelChangedHandler = ( diff --git a/public/app/plugins/panel/piechart/module.tsx b/public/app/plugins/panel/piechart/module.tsx index 57c1ec8d209..ccb0ae7f892 100644 --- a/public/app/plugins/panel/piechart/module.tsx +++ b/public/app/plugins/panel/piechart/module.tsx @@ -1,11 +1,13 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data'; -import { PieChartPanel } from './PieChartPanel'; -import { PieChartOptions, PieChartType, PieChartLabels, PieChartLegendValues } from './types'; import { LegendDisplayMode } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; -import { PieChartPanelChangedHandler } from './migrations'; + import { addStandardDataReduceOptions } from '../stat/types'; + +import { PieChartPanel } from './PieChartPanel'; +import { PieChartPanelChangedHandler } from './migrations'; import { PieChartSuggestionsSupplier } from './suggestions'; +import { PieChartOptions, PieChartType, PieChartLabels, PieChartLegendValues } from './types'; export const plugin = new PanelPlugin(PieChartPanel) .setPanelChangeHandler(PieChartPanelChangedHandler) diff --git a/public/app/plugins/panel/piechart/suggestions.ts b/public/app/plugins/panel/piechart/suggestions.ts index aa3f0b055cc..4eae227cd4e 100644 --- a/public/app/plugins/panel/piechart/suggestions.ts +++ b/public/app/plugins/panel/piechart/suggestions.ts @@ -1,6 +1,7 @@ import { VisualizationSuggestionsBuilder } from '@grafana/data'; import { LegendDisplayMode } from '@grafana/schema'; import { SuggestionName } from 'app/types/suggestions'; + import { PieChartLabels, PieChartOptions, PieChartType } from './types'; export class PieChartSuggestionsSupplier { diff --git a/public/app/plugins/panel/stat/StatMigrations.test.ts b/public/app/plugins/panel/stat/StatMigrations.test.ts index 46b5ce23a18..df6f173e1eb 100644 --- a/public/app/plugins/panel/stat/StatMigrations.test.ts +++ b/public/app/plugins/panel/stat/StatMigrations.test.ts @@ -1,8 +1,9 @@ import { PanelModel } from '@grafana/data'; -import { statPanelChangedHandler } from './StatMigrations'; import { BigValueGraphMode, BigValueColorMode } from '@grafana/ui'; import { BigValueTextMode } from '@grafana/ui/src/components/BigValue/BigValue'; +import { statPanelChangedHandler } from './StatMigrations'; + describe('Stat Panel Migrations', () => { it('change from angular singlestat sparkline disabled', () => { const old: any = { diff --git a/public/app/plugins/panel/stat/StatMigrations.ts b/public/app/plugins/panel/stat/StatMigrations.ts index f7f7bfb49b6..69415e97c65 100644 --- a/public/app/plugins/panel/stat/StatMigrations.ts +++ b/public/app/plugins/panel/stat/StatMigrations.ts @@ -1,8 +1,9 @@ -import { sharedSingleStatPanelChangedHandler, BigValueGraphMode, BigValueColorMode } from '@grafana/ui'; import { FieldColorModeId, FieldConfigSource, PanelModel } from '@grafana/data'; -import { StatPanelOptions } from './types'; +import { sharedSingleStatPanelChangedHandler, BigValueGraphMode, BigValueColorMode } from '@grafana/ui'; import { BigValueTextMode } from '@grafana/ui/src/components/BigValue/BigValue'; +import { StatPanelOptions } from './types'; + // This is called when the panel changes from another panel export const statPanelChangedHandler = ( panel: PanelModel> | any, diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index 13a62627b47..8515457b970 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -1,12 +1,6 @@ +import { isNumber } from 'lodash'; import React, { PureComponent } from 'react'; -import { - BigValue, - BigValueGraphMode, - DataLinksContextMenu, - VizRepeater, - VizRepeaterRenderValueProps, - BigValueTextMode, -} from '@grafana/ui'; + import { DisplayValueAlignmentFactors, FieldDisplay, @@ -16,12 +10,19 @@ import { NumericRange, PanelProps, } from '@grafana/data'; - -import { config } from 'app/core/config'; -import { StatPanelOptions } from './types'; -import { DataLinksContextMenuApi } from '@grafana/ui/src/components/DataLinks/DataLinksContextMenu'; import { findNumericFieldMinMax } from '@grafana/data/src/field/fieldOverrides'; -import { isNumber } from 'lodash'; +import { + BigValue, + BigValueGraphMode, + DataLinksContextMenu, + VizRepeater, + VizRepeaterRenderValueProps, + BigValueTextMode, +} from '@grafana/ui'; +import { DataLinksContextMenuApi } from '@grafana/ui/src/components/DataLinks/DataLinksContextMenu'; +import { config } from 'app/core/config'; + +import { StatPanelOptions } from './types'; export class StatPanel extends PureComponent> { renderComponent = ( diff --git a/public/app/plugins/panel/stat/module.tsx b/public/app/plugins/panel/stat/module.tsx index 411a5607ee4..8b4cfd2a7ed 100644 --- a/public/app/plugins/panel/stat/module.tsx +++ b/public/app/plugins/panel/stat/module.tsx @@ -1,14 +1,15 @@ +import { PanelPlugin } from '@grafana/data'; import { BigValueColorMode, BigValueTextMode, commonOptionsBuilder, sharedSingleStatMigrationHandler, } from '@grafana/ui'; -import { PanelPlugin } from '@grafana/data'; -import { addOrientationOption, addStandardDataReduceOptions, StatPanelOptions } from './types'; -import { StatPanel } from './StatPanel'; + import { statPanelChangedHandler } from './StatMigrations'; +import { StatPanel } from './StatPanel'; import { StatSuggestionsSupplier } from './suggestions'; +import { addOrientationOption, addStandardDataReduceOptions, StatPanelOptions } from './types'; export const plugin = new PanelPlugin(StatPanel) .useFieldConfig() diff --git a/public/app/plugins/panel/stat/suggestions.ts b/public/app/plugins/panel/stat/suggestions.ts index b94620205d4..60346f9b456 100644 --- a/public/app/plugins/panel/stat/suggestions.ts +++ b/public/app/plugins/panel/stat/suggestions.ts @@ -1,6 +1,7 @@ import { VisualizationSuggestionsBuilder } from '@grafana/data'; import { BigValueColorMode, BigValueGraphMode } from '@grafana/ui'; import { SuggestionName } from 'app/types/suggestions'; + import { StatPanelOptions } from './types'; export class StatSuggestionsSupplier { diff --git a/public/app/plugins/panel/stat/types.ts b/public/app/plugins/panel/stat/types.ts index b43c6248609..049d778af8e 100644 --- a/public/app/plugins/panel/stat/types.ts +++ b/public/app/plugins/panel/stat/types.ts @@ -1,10 +1,3 @@ -import { - SingleStatBaseOptions, - BigValueColorMode, - BigValueGraphMode, - BigValueJustifyMode, - BigValueTextMode, -} from '@grafana/ui'; import { ReducerID, standardEditorsRegistry, @@ -14,6 +7,13 @@ import { VizOrientation, PanelOptionsEditorBuilder, } from '@grafana/data'; +import { + SingleStatBaseOptions, + BigValueColorMode, + BigValueGraphMode, + BigValueJustifyMode, + BigValueTextMode, +} from '@grafana/ui'; // Structure copied from angular export interface StatPanelOptions extends SingleStatBaseOptions { diff --git a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx index 265b3b3d86c..54d5ea2f0b0 100755 --- a/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx @@ -1,12 +1,16 @@ import React, { useCallback, useMemo } from 'react'; + import { DataFrame, FieldType, PanelProps } from '@grafana/data'; import { TooltipPlugin, useTheme2, ZoomPlugin, usePanelContext } from '@grafana/ui'; -import { TimelineMode, TimelineOptions } from './types'; -import { TimelineChart } from './TimelineChart'; -import { prepareTimelineFields, prepareTimelineLegendItems } from './utils'; -import { StateTimelineTooltip } from './StateTimelineTooltip'; import { getLastStreamingDataFramePacket } from 'app/features/live/data/StreamingDataFrame'; +import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin'; + +import { StateTimelineTooltip } from './StateTimelineTooltip'; +import { TimelineChart } from './TimelineChart'; +import { TimelineMode, TimelineOptions } from './types'; +import { prepareTimelineFields, prepareTimelineLegendItems } from './utils'; + interface TimelinePanelProps extends PanelProps {} /** @@ -114,6 +118,7 @@ export const StateTimelinePanel: React.FC = ({ timeZone={timeZone} renderTooltip={renderCustomTooltip} /> + ); }} diff --git a/public/app/plugins/panel/state-timeline/StateTimelineTooltip.tsx b/public/app/plugins/panel/state-timeline/StateTimelineTooltip.tsx index c846ab5235a..b09926cdb73 100644 --- a/public/app/plugins/panel/state-timeline/StateTimelineTooltip.tsx +++ b/public/app/plugins/panel/state-timeline/StateTimelineTooltip.tsx @@ -1,6 +1,8 @@ import React from 'react'; + import { DataFrame, FALLBACK_COLOR, getDisplayProcessor, getFieldDisplayName, TimeZone } from '@grafana/data'; import { SeriesTableRow, useTheme2 } from '@grafana/ui'; + import { findNextStateIndex, fmtDuration } from './utils'; interface StateTimelineTooltipProps { diff --git a/public/app/plugins/panel/state-timeline/TimelineChart.tsx b/public/app/plugins/panel/state-timeline/TimelineChart.tsx index 72ee7e27640..3d13fc495cf 100755 --- a/public/app/plugins/panel/state-timeline/TimelineChart.tsx +++ b/public/app/plugins/panel/state-timeline/TimelineChart.tsx @@ -1,4 +1,6 @@ import React from 'react'; + +import { DataFrame, FALLBACK_COLOR, FieldType, TimeRange } from '@grafana/data'; import { LegendDisplayMode, VisibilityMode } from '@grafana/schema'; import { PanelContext, @@ -10,9 +12,9 @@ import { VizLegend, VizLegendItem, } from '@grafana/ui'; -import { DataFrame, FALLBACK_COLOR, FieldType, TimeRange } from '@grafana/data'; -import { preparePlotConfigBuilder } from './utils'; + import { TimelineMode, TimelineOptions, TimelineValueAlignment } from './types'; +import { preparePlotConfigBuilder } from './utils'; /** * @alpha diff --git a/public/app/plugins/panel/state-timeline/migrations.test.ts b/public/app/plugins/panel/state-timeline/migrations.test.ts index e93d6aa067b..f849be085c0 100644 --- a/public/app/plugins/panel/state-timeline/migrations.test.ts +++ b/public/app/plugins/panel/state-timeline/migrations.test.ts @@ -1,4 +1,5 @@ import { PanelModel } from 'app/features/dashboard/state'; + import { timelinePanelChangedHandler } from './migrations'; describe('Timeline Migrations', () => { diff --git a/public/app/plugins/panel/state-timeline/migrations.ts b/public/app/plugins/panel/state-timeline/migrations.ts index 83a3f1ef46f..35707074624 100644 --- a/public/app/plugins/panel/state-timeline/migrations.ts +++ b/public/app/plugins/panel/state-timeline/migrations.ts @@ -1,7 +1,9 @@ -import { FieldConfigSource, MappingType, PanelModel, ValueMap } from '@grafana/data'; -import { TimelineFieldConfig, TimelineOptions } from './types'; import { isArray } from 'lodash'; +import { FieldConfigSource, MappingType, PanelModel, ValueMap } from '@grafana/data'; + +import { TimelineFieldConfig, TimelineOptions } from './types'; + // This is called when the panel changes from another panel export const timelinePanelChangedHandler = ( panel: PanelModel> | any, diff --git a/public/app/plugins/panel/state-timeline/module.tsx b/public/app/plugins/panel/state-timeline/module.tsx index 2aed7c0165b..b807fb42f4a 100755 --- a/public/app/plugins/panel/state-timeline/module.tsx +++ b/public/app/plugins/panel/state-timeline/module.tsx @@ -5,13 +5,15 @@ import { identityOverrideProcessor, PanelPlugin, } from '@grafana/data'; -import { StateTimelinePanel } from './StateTimelinePanel'; -import { TimelineOptions, TimelineFieldConfig, defaultPanelOptions, defaultTimelineFieldConfig } from './types'; import { VisibilityMode } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; + +import { SpanNullsEditor } from '../timeseries/SpanNullsEditor'; + +import { StateTimelinePanel } from './StateTimelinePanel'; import { timelinePanelChangedHandler } from './migrations'; import { StatTimelineSuggestionsSupplier } from './suggestions'; -import { SpanNullsEditor } from '../timeseries/SpanNullsEditor'; +import { TimelineOptions, TimelineFieldConfig, defaultPanelOptions, defaultTimelineFieldConfig } from './types'; export const plugin = new PanelPlugin(StateTimelinePanel) .setPanelChangeHandler(timelinePanelChangedHandler) diff --git a/public/app/plugins/panel/state-timeline/suggestions.ts b/public/app/plugins/panel/state-timeline/suggestions.ts index 1fe768bf381..31ec82210a2 100644 --- a/public/app/plugins/panel/state-timeline/suggestions.ts +++ b/public/app/plugins/panel/state-timeline/suggestions.ts @@ -1,5 +1,6 @@ import { VisualizationSuggestionsBuilder } from '@grafana/data'; import { SuggestionName } from 'app/types/suggestions'; + import { TimelineFieldConfig, TimelineOptions } from './types'; export class StatTimelineSuggestionsSupplier { diff --git a/public/app/plugins/panel/state-timeline/timeline.ts b/public/app/plugins/panel/state-timeline/timeline.ts index d81d2384db8..317b2540340 100644 --- a/public/app/plugins/panel/state-timeline/timeline.ts +++ b/public/app/plugins/panel/state-timeline/timeline.ts @@ -1,11 +1,13 @@ import uPlot, { Cursor, Series } from 'uplot'; -import { FIXED_UNIT } from '@grafana/ui/src/components/GraphNG/GraphNG'; -import { pointWithin, Quadtree, Rect } from 'app/plugins/panel/barchart/quadtree'; -import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute'; -import { TimelineFieldConfig, TimelineMode, TimelineValueAlignment } from './types'; + import { GrafanaTheme2, TimeRange } from '@grafana/data'; -import { VisibilityMode } from '@grafana/schema'; import { alpha } from '@grafana/data/src/themes/colorManipulator'; +import { VisibilityMode } from '@grafana/schema'; +import { FIXED_UNIT } from '@grafana/ui/src/components/GraphNG/GraphNG'; +import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute'; +import { pointWithin, Quadtree, Rect } from 'app/plugins/panel/barchart/quadtree'; + +import { TimelineFieldConfig, TimelineMode, TimelineValueAlignment } from './types'; const { round, min, ceil } = Math; diff --git a/public/app/plugins/panel/state-timeline/utils.test.ts b/public/app/plugins/panel/state-timeline/utils.test.ts index 0382bf21bd0..19dc9f188bf 100644 --- a/public/app/plugins/panel/state-timeline/utils.test.ts +++ b/public/app/plugins/panel/state-timeline/utils.test.ts @@ -1,5 +1,6 @@ import { ArrayVector, createTheme, FieldType, ThresholdsMode, toDataFrame } from '@grafana/data'; import { LegendDisplayMode } from '@grafana/schema'; + import { findNextStateIndex, fmtDuration, diff --git a/public/app/plugins/panel/state-timeline/utils.ts b/public/app/plugins/panel/state-timeline/utils.ts index e84b6a87fbb..9d0e7e44625 100644 --- a/public/app/plugins/panel/state-timeline/utils.ts +++ b/public/app/plugins/panel/state-timeline/utils.ts @@ -1,4 +1,6 @@ import React from 'react'; +import uPlot from 'uplot'; + import { ArrayVector, DataFrame, @@ -20,6 +22,7 @@ import { getFieldConfigWithMinMax, ThresholdsMode, } from '@grafana/data'; +import { VizLegendOptions, AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/schema'; import { FIXED_UNIT, SeriesVisibilityChangeMode, @@ -27,12 +30,12 @@ import { UPlotConfigPrepFn, VizLegendItem, } from '@grafana/ui'; -import { getConfig, TimelineCoreOptions } from './timeline'; -import { VizLegendOptions, AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/schema'; -import { TimelineFieldConfig, TimelineOptions } from './types'; import { PlotTooltipInterpolator } from '@grafana/ui/src/components/uPlot/types'; + import { preparePlotData2, getStackingGroups } from '../../../../../packages/grafana-ui/src/components/uPlot/utils'; -import uPlot from 'uplot'; + +import { getConfig, TimelineCoreOptions } from './timeline'; +import { TimelineFieldConfig, TimelineOptions } from './types'; const defaultConfig: TimelineFieldConfig = { lineWidth: 0, diff --git a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx index 4552ac8477e..3cfea9dc0cb 100755 --- a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx @@ -1,10 +1,14 @@ import React, { useMemo } from 'react'; + import { PanelProps } from '@grafana/data'; import { TooltipPlugin, useTheme2, ZoomPlugin } from '@grafana/ui'; -import { StatusPanelOptions } from './types'; + import { TimelineChart } from '../state-timeline/TimelineChart'; import { TimelineMode } from '../state-timeline/types'; import { prepareTimelineFields, prepareTimelineLegendItems } from '../state-timeline/utils'; +import { OutsideRangePlugin } from '../timeseries/plugins/OutsideRangePlugin'; + +import { StatusPanelOptions } from './types'; interface TimelinePanelProps extends PanelProps {} @@ -68,6 +72,7 @@ export const StatusHistoryPanel: React.FC = ({ <> + ); }} diff --git a/public/app/plugins/panel/status-history/module.tsx b/public/app/plugins/panel/status-history/module.tsx index 65be04c368b..1f961295ce6 100755 --- a/public/app/plugins/panel/status-history/module.tsx +++ b/public/app/plugins/panel/status-history/module.tsx @@ -1,9 +1,10 @@ import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data'; -import { StatusHistoryPanel } from './StatusHistoryPanel'; -import { StatusPanelOptions, StatusFieldConfig, defaultStatusFieldConfig } from './types'; import { VisibilityMode } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; + +import { StatusHistoryPanel } from './StatusHistoryPanel'; import { StatusHistorySuggestionsSupplier } from './suggestions'; +import { StatusPanelOptions, StatusFieldConfig, defaultStatusFieldConfig } from './types'; export const plugin = new PanelPlugin(StatusHistoryPanel) .useFieldConfig({ diff --git a/public/app/plugins/panel/status-history/suggestions.ts b/public/app/plugins/panel/status-history/suggestions.ts index cac89f49d8c..37745263c81 100644 --- a/public/app/plugins/panel/status-history/suggestions.ts +++ b/public/app/plugins/panel/status-history/suggestions.ts @@ -1,5 +1,6 @@ import { FieldColorModeId, VisualizationSuggestionsBuilder } from '@grafana/data'; import { SuggestionName } from 'app/types/suggestions'; + import { StatusPanelOptions, StatusFieldConfig } from './types'; export class StatusHistorySuggestionsSupplier { diff --git a/public/app/plugins/panel/table-old/column_options.ts b/public/app/plugins/panel/table-old/column_options.ts index b196e85a9fd..c3543167e1c 100644 --- a/public/app/plugins/panel/table-old/column_options.ts +++ b/public/app/plugins/panel/table-old/column_options.ts @@ -1,4 +1,5 @@ import { map, without } from 'lodash'; + import { getValueFormats } from '@grafana/data'; export class ColumnOptionsCtrl { diff --git a/public/app/plugins/panel/table-old/editor.ts b/public/app/plugins/panel/table-old/editor.ts index 15a60dc32fa..858de542fb3 100644 --- a/public/app/plugins/panel/table-old/editor.ts +++ b/public/app/plugins/panel/table-old/editor.ts @@ -1,4 +1,5 @@ import { find, map, without } from 'lodash'; + import { transformers } from './transformers'; import { ColumnStyle } from './types'; diff --git a/public/app/plugins/panel/table-old/module.ts b/public/app/plugins/panel/table-old/module.ts index 854ba22dd1e..548a828f87d 100644 --- a/public/app/plugins/panel/table-old/module.ts +++ b/public/app/plugins/panel/table-old/module.ts @@ -1,15 +1,17 @@ -import { defaults } from 'lodash'; import $ from 'jquery'; -import { MetricsPanelCtrl } from 'app/plugins/sdk'; -import config from 'app/core/config'; -import { transformDataToTable } from './transformers'; -import { tablePanelEditor } from './editor'; -import { columnOptionsTab } from './column_options'; -import { TableRenderer } from './renderer'; -import { isTableData, PanelEvents, PanelPlugin, PanelProps } from '@grafana/data'; -import { dispatch } from 'app/store/store'; +import { defaults } from 'lodash'; import { ComponentType } from 'react'; + +import { isTableData, PanelEvents, PanelPlugin, PanelProps } from '@grafana/data'; +import config from 'app/core/config'; import { applyFilterFromTable } from 'app/features/variables/adhoc/actions'; +import { MetricsPanelCtrl } from 'app/plugins/sdk'; +import { dispatch } from 'app/store/store'; + +import { columnOptionsTab } from './column_options'; +import { tablePanelEditor } from './editor'; +import { TableRenderer } from './renderer'; +import { transformDataToTable } from './transformers'; export class TablePanelCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; diff --git a/public/app/plugins/panel/table-old/renderer.ts b/public/app/plugins/panel/table-old/renderer.ts index 43b0520617f..550ad34adc9 100644 --- a/public/app/plugins/panel/table-old/renderer.ts +++ b/public/app/plugins/panel/table-old/renderer.ts @@ -1,4 +1,5 @@ import { find, first, isArray, isString, escape } from 'lodash'; + import { escapeStringForRegex, formattedValueToString, @@ -15,8 +16,9 @@ import { GrafanaTheme, } from '@grafana/data'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; -import { ColumnRender, TableRenderModel, ColumnStyle } from './types'; + import { ColumnOptionsCtrl } from './column_options'; +import { ColumnRender, TableRenderModel, ColumnStyle } from './types'; export class TableRenderer { formatters: any[] = []; diff --git a/public/app/plugins/panel/table-old/specs/renderer.test.ts b/public/app/plugins/panel/table-old/specs/renderer.test.ts index 25c99781f91..0b9120e037f 100644 --- a/public/app/plugins/panel/table-old/specs/renderer.test.ts +++ b/public/app/plugins/panel/table-old/specs/renderer.test.ts @@ -1,9 +1,11 @@ import { each } from 'lodash'; -import TableModel from 'app/core/table_model'; -import { TableRenderer } from '../renderer'; + import { ScopedVars, TimeZone } from '@grafana/data'; -import { ColumnRender } from '../types'; import { getTheme } from '@grafana/ui'; +import TableModel from 'app/core/table_model'; + +import { TableRenderer } from '../renderer'; +import { ColumnRender } from '../types'; const utc: TimeZone = 'utc'; diff --git a/public/app/plugins/panel/table-old/transformers.ts b/public/app/plugins/panel/table-old/transformers.ts index b3b1c987613..c4b50796c4e 100644 --- a/public/app/plugins/panel/table-old/transformers.ts +++ b/public/app/plugins/panel/table-old/transformers.ts @@ -1,9 +1,11 @@ import { findIndex, isObject, map } from 'lodash'; -import flatten from 'app/core/utils/flatten'; -import TimeSeries from 'app/core/time_series2'; -import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; -import { TableTransform } from './types'; + import { Column, TableData } from '@grafana/data'; +import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; +import TimeSeries from 'app/core/time_series2'; +import flatten from 'app/core/utils/flatten'; + +import { TableTransform } from './types'; const transformers: { [key: string]: TableTransform } = {}; export const timeSeriesFormatFilterer = (data: any): any[] => { diff --git a/public/app/plugins/panel/table-old/types.ts b/public/app/plugins/panel/table-old/types.ts index 21c49166de0..a98f04d5837 100644 --- a/public/app/plugins/panel/table-old/types.ts +++ b/public/app/plugins/panel/table-old/types.ts @@ -1,5 +1,5 @@ -import TableModel from 'app/core/table_model'; import { Column } from '@grafana/data'; +import TableModel from 'app/core/table_model'; export interface TableTransform { description: string; diff --git a/public/app/plugins/panel/table/PaginationEditor.tsx b/public/app/plugins/panel/table/PaginationEditor.tsx index 241d3137de1..5d25249d845 100644 --- a/public/app/plugins/panel/table/PaginationEditor.tsx +++ b/public/app/plugins/panel/table/PaginationEditor.tsx @@ -1,6 +1,7 @@ +import React from 'react'; + import { StandardEditorProps } from '@grafana/data'; import { Switch } from '@grafana/ui'; -import React from 'react'; export function PaginationEditor({ onChange, value, context }: StandardEditorProps) { const changeValue = (event: React.FormEvent | undefined) => { diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index a55e7e21e2e..d5e16979514 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -1,5 +1,6 @@ +import { css } from '@emotion/css'; import React, { Component } from 'react'; -import { Select, Table } from '@grafana/ui'; + import { DataFrame, FieldMatcherID, @@ -8,16 +9,18 @@ import { PanelProps, SelectableValue, } from '@grafana/data'; -import { PanelOptions } from './models.gen'; -import { css } from '@emotion/css'; -import { config } from 'app/core/config'; -import { FilterItem, TableSortByFieldState } from '@grafana/ui/src/components/Table/types'; -import { dispatch } from '../../../store/store'; -import { applyFilterFromTable } from '../../../features/variables/adhoc/actions'; -import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; -import { getFooterCells } from './footer'; -import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { PanelDataErrorView } from '@grafana/runtime'; +import { Select, Table } from '@grafana/ui'; +import { FilterItem, TableSortByFieldState } from '@grafana/ui/src/components/Table/types'; +import { config } from 'app/core/config'; +import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; + +import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv'; +import { applyFilterFromTable } from '../../../features/variables/adhoc/actions'; +import { dispatch } from '../../../store/store'; + +import { getFooterCells } from './footer'; +import { PanelOptions } from './models.gen'; interface Props extends PanelProps {} diff --git a/public/app/plugins/panel/table/footer.ts b/public/app/plugins/panel/table/footer.ts index 3611cbbd371..0ac8fa5c80d 100644 --- a/public/app/plugins/panel/table/footer.ts +++ b/public/app/plugins/panel/table/footer.ts @@ -8,9 +8,10 @@ import { fieldReducers, } from '@grafana/data'; import { FooterItem } from '@grafana/ui/src/components/Table/types'; -import { TableFooterCalc } from './models.gen'; import { config } from 'app/core/config'; +import { TableFooterCalc } from './models.gen'; + export function getFooterCells(frame: DataFrame, options?: TableFooterCalc): FooterItem[] { return frame.fields.map((field, i) => { if (field.type !== FieldType.number) { diff --git a/public/app/plugins/panel/table/migrations.test.ts b/public/app/plugins/panel/table/migrations.test.ts index 2f767d0f95d..587d7907a7b 100644 --- a/public/app/plugins/panel/table/migrations.test.ts +++ b/public/app/plugins/panel/table/migrations.test.ts @@ -1,4 +1,5 @@ import { PanelModel } from '@grafana/data'; + import { tablePanelChangedHandler } from './migrations'; describe('Table Migrations', () => { diff --git a/public/app/plugins/panel/table/migrations.ts b/public/app/plugins/panel/table/migrations.ts index fc461d66ed2..5b30adf3dc7 100644 --- a/public/app/plugins/panel/table/migrations.ts +++ b/public/app/plugins/panel/table/migrations.ts @@ -1,3 +1,5 @@ +import { omitBy, isNil, isNumber, defaultTo } from 'lodash'; + import { PanelModel, FieldMatcherID, @@ -7,7 +9,7 @@ import { FieldConfig, } from '@grafana/data'; import { ReduceTransformerOptions } from '@grafana/data/src/transformations/transformers/reduce'; -import { omitBy, isNil, isNumber, defaultTo } from 'lodash'; + import { PanelOptions } from './models.gen'; /** diff --git a/public/app/plugins/panel/table/module.tsx b/public/app/plugins/panel/table/module.tsx index bfc3dfdccd0..655597ec4c0 100644 --- a/public/app/plugins/panel/table/module.tsx +++ b/public/app/plugins/panel/table/module.tsx @@ -6,13 +6,14 @@ import { ReducerID, standardEditorsRegistry, } from '@grafana/data'; -import { TablePanel } from './TablePanel'; -import { PanelOptions, defaultPanelOptions, defaultPanelFieldConfig } from './models.gen'; import { TableFieldOptions } from '@grafana/schema'; -import { tableMigrationHandler, tablePanelChangedHandler } from './migrations'; import { TableCellDisplayMode } from '@grafana/ui'; -import { TableSuggestionsSupplier } from './suggestions'; + import { PaginationEditor } from './PaginationEditor'; +import { TablePanel } from './TablePanel'; +import { tableMigrationHandler, tablePanelChangedHandler } from './migrations'; +import { PanelOptions, defaultPanelOptions, defaultPanelFieldConfig } from './models.gen'; +import { TableSuggestionsSupplier } from './suggestions'; export const plugin = new PanelPlugin(TablePanel) .setPanelChangeHandler(tablePanelChangedHandler) diff --git a/public/app/plugins/panel/table/suggestions.ts b/public/app/plugins/panel/table/suggestions.ts index 3294de7839d..c9b1d89a7fc 100644 --- a/public/app/plugins/panel/table/suggestions.ts +++ b/public/app/plugins/panel/table/suggestions.ts @@ -1,6 +1,7 @@ import { VisualizationSuggestionsBuilder } from '@grafana/data'; import { TableFieldOptions } from '@grafana/schema'; import { SuggestionName } from 'app/types/suggestions'; + import { PanelOptions } from './models.gen'; export class TableSuggestionsSupplier { diff --git a/public/app/plugins/panel/text/TextPanel.tsx b/public/app/plugins/panel/text/TextPanel.tsx index b9e918ce259..f2808f00cf8 100644 --- a/public/app/plugins/panel/text/TextPanel.tsx +++ b/public/app/plugins/panel/text/TextPanel.tsx @@ -1,14 +1,16 @@ // Libraries -import React, { PureComponent } from 'react'; -import { debounce } from 'lodash'; -import { PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data'; -// Utils -import config from 'app/core/config'; -// Types -import { PanelOptions, TextMode } from './models.gen'; -import { CustomScrollbar, stylesFactory } from '@grafana/ui'; import { css, cx } from '@emotion/css'; import DangerouslySetHtmlContent from 'dangerously-set-html-content'; +import { debounce } from 'lodash'; +import React, { PureComponent } from 'react'; + +import { PanelProps, renderTextPanelMarkdown, textUtil } from '@grafana/data'; +// Utils +import { CustomScrollbar, stylesFactory } from '@grafana/ui'; +import config from 'app/core/config'; + +// Types +import { PanelOptions, TextMode } from './models.gen'; interface Props extends PanelProps {} diff --git a/public/app/plugins/panel/text/TextPanelEditor.tsx b/public/app/plugins/panel/text/TextPanelEditor.tsx index a8c1a351c2f..a44e161f1fc 100644 --- a/public/app/plugins/panel/text/TextPanelEditor.tsx +++ b/public/app/plugins/panel/text/TextPanelEditor.tsx @@ -1,6 +1,8 @@ -import React, { FC, useMemo } from 'react'; import { css, cx } from '@emotion/css'; +import React, { FC, useMemo } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; + +import { GrafanaTheme, StandardEditorProps } from '@grafana/data'; import { CodeEditor, stylesFactory, @@ -8,7 +10,6 @@ import { CodeEditorSuggestionItem, variableSuggestionToCodeEditorSuggestion, } from '@grafana/ui'; -import { GrafanaTheme, StandardEditorProps } from '@grafana/data'; import { PanelOptions, TextMode } from './models.gen'; diff --git a/public/app/plugins/panel/text/module.tsx b/public/app/plugins/panel/text/module.tsx index f30a4492ca7..66f6b127a1e 100644 --- a/public/app/plugins/panel/text/module.tsx +++ b/public/app/plugins/panel/text/module.tsx @@ -1,9 +1,9 @@ import { PanelPlugin } from '@grafana/data'; import { TextPanel } from './TextPanel'; -import { textPanelMigrationHandler } from './textPanelMigrationHandler'; import { TextPanelEditor } from './TextPanelEditor'; import { defaultPanelOptions, PanelOptions, TextMode } from './models.gen'; +import { textPanelMigrationHandler } from './textPanelMigrationHandler'; export const plugin = new PanelPlugin(TextPanel) .setPanelOptions((builder) => { diff --git a/public/app/plugins/panel/text/textPanelMigrationHandler.test.ts b/public/app/plugins/panel/text/textPanelMigrationHandler.test.ts index 1d9c6e0b809..39a072a8c23 100644 --- a/public/app/plugins/panel/text/textPanelMigrationHandler.test.ts +++ b/public/app/plugins/panel/text/textPanelMigrationHandler.test.ts @@ -1,7 +1,8 @@ -import { textPanelMigrationHandler } from './textPanelMigrationHandler'; -import { TextMode, PanelOptions } from './models.gen'; import { FieldConfigSource, PanelModel } from '@grafana/data'; +import { TextMode, PanelOptions } from './models.gen'; +import { textPanelMigrationHandler } from './textPanelMigrationHandler'; + describe('textPanelMigrationHandler', () => { describe('when invoked and previous version was old Angular text panel', () => { it('then should migrate options', () => { diff --git a/public/app/plugins/panel/text/textPanelMigrationHandler.ts b/public/app/plugins/panel/text/textPanelMigrationHandler.ts index 23ffcaeb927..c3eeed14f8b 100644 --- a/public/app/plugins/panel/text/textPanelMigrationHandler.ts +++ b/public/app/plugins/panel/text/textPanelMigrationHandler.ts @@ -1,4 +1,5 @@ import { PanelModel } from '@grafana/data'; + import { TextMode, PanelOptions } from './models.gen'; export const textPanelMigrationHandler = (panel: PanelModel): Partial => { diff --git a/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx b/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx index a8ca720f466..d2614d3ae71 100644 --- a/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx +++ b/public/app/plugins/panel/timeseries/FillBelowToEditor.tsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; + import { FieldOverrideEditorProps, FieldType, getFieldDisplayName, SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; diff --git a/public/app/plugins/panel/timeseries/LineStyleEditor.tsx b/public/app/plugins/panel/timeseries/LineStyleEditor.tsx index 75a089ce70e..8807bbe118f 100644 --- a/public/app/plugins/panel/timeseries/LineStyleEditor.tsx +++ b/public/app/plugins/panel/timeseries/LineStyleEditor.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; -import { LineStyle } from '@grafana/schema'; + import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data'; +import { LineStyle } from '@grafana/schema'; import { HorizontalGroup, IconButton, RadioButtonGroup, Select } from '@grafana/ui'; type LineFill = 'solid' | 'dash' | 'dot'; diff --git a/public/app/plugins/panel/timeseries/SpanNullsEditor.tsx b/public/app/plugins/panel/timeseries/SpanNullsEditor.tsx index 12525dd73bf..119ff689238 100644 --- a/public/app/plugins/panel/timeseries/SpanNullsEditor.tsx +++ b/public/app/plugins/panel/timeseries/SpanNullsEditor.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { FieldOverrideEditorProps, rangeUtil, SelectableValue } from '@grafana/data'; import { HorizontalGroup, Input, RadioButtonGroup } from '@grafana/ui'; diff --git a/public/app/plugins/panel/timeseries/ThresholdsStyleEditor.tsx b/public/app/plugins/panel/timeseries/ThresholdsStyleEditor.tsx index 83e202cd16e..40bd69deb83 100644 --- a/public/app/plugins/panel/timeseries/ThresholdsStyleEditor.tsx +++ b/public/app/plugins/panel/timeseries/ThresholdsStyleEditor.tsx @@ -1,6 +1,7 @@ import React, { useCallback } from 'react'; -import { GraphTresholdsStyleMode } from '@grafana/schema'; + import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data'; +import { GraphTresholdsStyleMode } from '@grafana/schema'; import { Select } from '@grafana/ui'; export const ThresholdsStyleEditor: React.FC< diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 5eb7e489b41..9e143d4f976 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -1,17 +1,20 @@ import React, { useMemo } from 'react'; + import { Field, PanelProps } from '@grafana/data'; +import { PanelDataErrorView } from '@grafana/runtime'; import { TooltipDisplayMode } from '@grafana/schema'; import { usePanelContext, TimeSeries, TooltipPlugin, ZoomPlugin, KeyboardPlugin } from '@grafana/ui'; +import { config } from 'app/core/config'; import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; + +import { AnnotationEditorPlugin } from './plugins/AnnotationEditorPlugin'; import { AnnotationsPlugin } from './plugins/AnnotationsPlugin'; import { ContextMenuPlugin } from './plugins/ContextMenuPlugin'; import { ExemplarsPlugin } from './plugins/ExemplarsPlugin'; +import { OutsideRangePlugin } from './plugins/OutsideRangePlugin'; +import { ThresholdControlsPlugin } from './plugins/ThresholdControlsPlugin'; import { TimeSeriesOptions } from './types'; import { prepareGraphableFields } from './utils'; -import { AnnotationEditorPlugin } from './plugins/AnnotationEditorPlugin'; -import { ThresholdControlsPlugin } from './plugins/ThresholdControlsPlugin'; -import { config } from 'app/core/config'; -import { PanelDataErrorView } from '@grafana/runtime'; interface TimeSeriesPanelProps extends PanelProps {} @@ -134,6 +137,8 @@ export const TimeSeriesPanel: React.FC = ({ onThresholdsChange={onThresholdsChange} /> )} + + ); }} diff --git a/public/app/plugins/panel/timeseries/config.ts b/public/app/plugins/panel/timeseries/config.ts index dae74c592a2..b75ad5e5b68 100644 --- a/public/app/plugins/panel/timeseries/config.ts +++ b/public/app/plugins/panel/timeseries/config.ts @@ -18,11 +18,10 @@ import { GraphTresholdsStyleMode, GraphTransform, } from '@grafana/schema'; - import { graphFieldOptions, commonOptionsBuilder } from '@grafana/ui'; -import { LineStyleEditor } from './LineStyleEditor'; import { FillBellowToEditor } from './FillBelowToEditor'; +import { LineStyleEditor } from './LineStyleEditor'; import { SpanNullsEditor } from './SpanNullsEditor'; import { ThresholdsStyleEditor } from './ThresholdsStyleEditor'; diff --git a/public/app/plugins/panel/timeseries/migrations.test.ts b/public/app/plugins/panel/timeseries/migrations.test.ts index da9cf0275d4..767c528464a 100644 --- a/public/app/plugins/panel/timeseries/migrations.test.ts +++ b/public/app/plugins/panel/timeseries/migrations.test.ts @@ -1,8 +1,10 @@ -import { PanelModel, FieldConfigSource } from '@grafana/data'; -import { graphPanelChangedHandler } from './migrations'; import { cloneDeep } from 'lodash'; + +import { PanelModel, FieldConfigSource } from '@grafana/data'; import { TooltipDisplayMode, SortOrder } from '@grafana/schema'; +import { graphPanelChangedHandler } from './migrations'; + describe('Graph Migrations', () => { let prevFieldConfig: FieldConfigSource; diff --git a/public/app/plugins/panel/timeseries/migrations.ts b/public/app/plugins/panel/timeseries/migrations.ts index 8b336f8d3c1..56beb024450 100644 --- a/public/app/plugins/panel/timeseries/migrations.ts +++ b/public/app/plugins/panel/timeseries/migrations.ts @@ -1,3 +1,5 @@ +import { omitBy, pickBy, isNil, isNumber, isString } from 'lodash'; + import { ConfigOverrideRule, DynamicConfigValue, @@ -29,9 +31,9 @@ import { SortOrder, GraphTransform, } from '@grafana/schema'; -import { TimeSeriesOptions } from './types'; -import { omitBy, pickBy, isNil, isNumber, isString } from 'lodash'; + import { defaultGraphConfig } from './config'; +import { TimeSeriesOptions } from './types'; /** * This is called when the panel changes from another panel diff --git a/public/app/plugins/panel/timeseries/module.tsx b/public/app/plugins/panel/timeseries/module.tsx index 230a985cae6..c1f4b742ff9 100644 --- a/public/app/plugins/panel/timeseries/module.tsx +++ b/public/app/plugins/panel/timeseries/module.tsx @@ -1,11 +1,12 @@ -import { GraphFieldConfig } from '@grafana/schema'; import { PanelPlugin } from '@grafana/data'; +import { GraphFieldConfig } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; + import { TimeSeriesPanel } from './TimeSeriesPanel'; -import { graphPanelChangedHandler } from './migrations'; -import { TimeSeriesOptions } from './types'; import { defaultGraphConfig, getGraphFieldConfig } from './config'; +import { graphPanelChangedHandler } from './migrations'; import { TimeSeriesSuggestionsSupplier } from './suggestions'; +import { TimeSeriesOptions } from './types'; export const plugin = new PanelPlugin(TimeSeriesPanel) .setPanelChangeHandler(graphPanelChangedHandler) diff --git a/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts b/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts index c66d04ffeb3..08a8600bd29 100644 --- a/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts +++ b/public/app/plugins/panel/timeseries/overrides/colorSeriesConfigFactory.test.ts @@ -1,4 +1,5 @@ import { FieldColorModeId, FieldConfigSource, FieldMatcherID } from '@grafana/data'; + import { changeSeriesColorConfigFactory } from './colorSeriesConfigFactory'; describe('changeSeriesColorConfigFactory', () => { diff --git a/public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.test.ts b/public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.test.ts index 369dc0f4a18..059e65db4a8 100644 --- a/public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.test.ts +++ b/public/app/plugins/panel/timeseries/overrides/hideSeriesConfigFactory.test.ts @@ -7,6 +7,7 @@ import { toDataFrame, } from '@grafana/data'; import { GraphNGLegendEvent, SeriesVisibilityChangeMode } from '@grafana/ui'; + import { hideSeriesConfigFactory } from './hideSeriesConfigFactory'; describe('hideSeriesConfigFactory', () => { diff --git a/public/app/plugins/panel/timeseries/plugins/AnnotationEditorPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/AnnotationEditorPlugin.tsx index 43d0e6e8bc0..c66e91972f9 100644 --- a/public/app/plugins/panel/timeseries/plugins/AnnotationEditorPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/AnnotationEditorPlugin.tsx @@ -1,10 +1,12 @@ -import { CartesianCoords2D, DataFrame, TimeZone } from '@grafana/data'; -import { PlotSelection, UPlotConfigBuilder } from '@grafana/ui'; import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { useMountedState } from 'react-use'; -import { AnnotationEditor } from './annotations/AnnotationEditor'; import uPlot from 'uplot'; +import { CartesianCoords2D, DataFrame, TimeZone } from '@grafana/data'; +import { PlotSelection, UPlotConfigBuilder } from '@grafana/ui'; + +import { AnnotationEditor } from './annotations/AnnotationEditor'; + type StartAnnotatingFn = (props: { // pixel coordinates of the clicked point on the uPlot canvas coords: { viewport: CartesianCoords2D; plotCanvas: CartesianCoords2D } | null; diff --git a/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx index ad6a58b8ac8..8073bb61d38 100644 --- a/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/AnnotationsPlugin.tsx @@ -1,8 +1,10 @@ +import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react'; +import uPlot from 'uplot'; + import { colorManipulator, DataFrame, DataFrameFieldIndex, DataFrameView, TimeZone } from '@grafana/data'; import { EventsCanvas, UPlotConfigBuilder, useTheme2 } from '@grafana/ui'; -import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react'; + import { AnnotationMarker } from './annotations/AnnotationMarker'; -import uPlot from 'uplot'; interface AnnotationsPluginProps { config: UPlotConfigBuilder; diff --git a/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx index d5fbcd2e92b..b27a9f9880a 100644 --- a/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx @@ -1,5 +1,8 @@ -import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { css as cssCore, Global } from '@emotion/react'; +import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useClickAway } from 'react-use'; + +import { CartesianCoords2D, DataFrame, getFieldDisplayName, InterpolateFunction, TimeZone } from '@grafana/data'; import { ContextMenu, GraphContextMenuHeader, @@ -10,8 +13,6 @@ import { MenuItem, UPlotConfigBuilder, } from '@grafana/ui'; -import { CartesianCoords2D, DataFrame, getFieldDisplayName, InterpolateFunction, TimeZone } from '@grafana/data'; -import { useClickAway } from 'react-use'; import { pluginLog } from '@grafana/ui/src/components/uPlot/utils'; type ContextMenuSelectionCoords = { viewport: CartesianCoords2D; plotCanvas: CartesianCoords2D }; diff --git a/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx b/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx index b99bcfd5121..1a0112ef62c 100644 --- a/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx +++ b/public/app/plugins/panel/timeseries/plugins/ExemplarMarker.tsx @@ -1,4 +1,7 @@ import { css, cx } from '@emotion/css'; +import React, { useCallback, useRef, useState } from 'react'; +import { usePopper } from 'react-popper'; + import { DataFrame, DataFrameFieldIndex, @@ -12,8 +15,6 @@ import { } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { FieldLinkList, Portal, UPlotConfigBuilder, useStyles } from '@grafana/ui'; -import React, { useCallback, useRef, useState } from 'react'; -import { usePopper } from 'react-popper'; interface ExemplarMarkerProps { timeZone: TimeZone; diff --git a/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx index ea22594b674..6e8e663f940 100644 --- a/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/ExemplarsPlugin.tsx @@ -1,3 +1,6 @@ +import React, { useCallback, useLayoutEffect, useRef } from 'react'; +import uPlot from 'uplot'; + import { DataFrame, DataFrameFieldIndex, @@ -8,9 +11,8 @@ import { TIME_SERIES_VALUE_FIELD_NAME, } from '@grafana/data'; import { EventsCanvas, FIXED_UNIT, UPlotConfigBuilder } from '@grafana/ui'; -import React, { useCallback, useLayoutEffect, useRef } from 'react'; + import { ExemplarMarker } from './ExemplarMarker'; -import uPlot from 'uplot'; interface ExemplarsPluginProps { config: UPlotConfigBuilder; diff --git a/public/app/plugins/panel/timeseries/plugins/OutsideRangePlugin.tsx b/public/app/plugins/panel/timeseries/plugins/OutsideRangePlugin.tsx new file mode 100644 index 00000000000..6cf3cd30532 --- /dev/null +++ b/public/app/plugins/panel/timeseries/plugins/OutsideRangePlugin.tsx @@ -0,0 +1,58 @@ +import React, { useLayoutEffect, useRef } from 'react'; +import uPlot from 'uplot'; + +import { TimeRange, AbsoluteTimeRange } from '@grafana/data'; +import { UPlotConfigBuilder, Button } from '@grafana/ui'; + +interface ThresholdControlsPluginProps { + config: UPlotConfigBuilder; + range: TimeRange; + onChangeTimeRange: (timeRange: AbsoluteTimeRange) => void; +} + +export const OutsideRangePlugin: React.FC = ({ config, range, onChangeTimeRange }) => { + const plotInstance = useRef(); + + useLayoutEffect(() => { + config.addHook('init', (u) => { + plotInstance.current = u; + }); + }, [config]); + + const timevalues = plotInstance.current?.data?.[0]; + if (!timevalues || !plotInstance.current || timevalues.length < 2 || !onChangeTimeRange) { + return null; + } + + // Time values are always sorted for uPlot to work + const first = timevalues[0]; + const last = timevalues[timevalues.length - 1]; + const fromX = range.from.valueOf(); + const toX = range.to.valueOf(); + + // (StartA <= EndB) and (EndA >= StartB) + if (first <= toX && last >= fromX) { + return null; + } + + return ( +
+
+
Data outside time range
+ +
+
+ ); +}; + +OutsideRangePlugin.displayName = 'OutsideRangePlugin'; diff --git a/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx b/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx index 34cb44742a1..84c766b3032 100644 --- a/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx +++ b/public/app/plugins/panel/timeseries/plugins/ThresholdControlsPlugin.tsx @@ -1,8 +1,10 @@ import React, { useState, useLayoutEffect, useMemo, useRef } from 'react'; +import uPlot from 'uplot'; + import { FieldConfigSource, ThresholdsConfig, getValueFormat } from '@grafana/data'; import { UPlotConfigBuilder, buildScaleKey } from '@grafana/ui'; + import { ThresholdDragHandle } from './ThresholdDragHandle'; -import uPlot from 'uplot'; const GUTTER_SIZE = 60; diff --git a/public/app/plugins/panel/timeseries/plugins/ThresholdDragHandle.tsx b/public/app/plugins/panel/timeseries/plugins/ThresholdDragHandle.tsx index 33430101ad8..605887c8d05 100644 --- a/public/app/plugins/panel/timeseries/plugins/ThresholdDragHandle.tsx +++ b/public/app/plugins/panel/timeseries/plugins/ThresholdDragHandle.tsx @@ -1,8 +1,9 @@ -import React, { useMemo, useState } from 'react'; import { css } from '@emotion/css'; +import React, { useMemo, useState } from 'react'; +import Draggable, { DraggableBounds } from 'react-draggable'; + import { Threshold, GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '@grafana/ui'; -import Draggable, { DraggableBounds } from 'react-draggable'; type OutOfBounds = 'top' | 'bottom' | 'none'; diff --git a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx index 6186365b1f8..b140d9e80ec 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditor.tsx @@ -1,9 +1,12 @@ +import { css, cx } from '@emotion/css'; import React, { HTMLAttributes, useState } from 'react'; import { usePopper } from 'react-popper'; -import { css, cx } from '@emotion/css'; -import { PlotSelection, useStyles2, useTheme2, Portal, DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; + import { colorManipulator, DataFrame, getDisplayProcessor, GrafanaTheme2, TimeZone } from '@grafana/data'; +import { PlotSelection, useStyles2, useTheme2, Portal, DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; + import { getCommonAnnotationStyles } from '../styles'; + import { AnnotationEditorForm } from './AnnotationEditorForm'; interface AnnotationEditorProps extends HTMLAttributes { diff --git a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditorForm.tsx b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditorForm.tsx index fb26a82951a..edc4cfd149e 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditorForm.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationEditorForm.tsx @@ -1,9 +1,10 @@ -import React, { HTMLAttributes, useRef } from 'react'; import { css, cx } from '@emotion/css'; -import { Button, Field, Form, HorizontalGroup, InputControl, TextArea, usePanelContext, useStyles2 } from '@grafana/ui'; -import { AnnotationEventUIModel, GrafanaTheme2 } from '@grafana/data'; -import useClickAway from 'react-use/lib/useClickAway'; +import React, { HTMLAttributes, useRef } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; +import useClickAway from 'react-use/lib/useClickAway'; + +import { AnnotationEventUIModel, GrafanaTheme2 } from '@grafana/data'; +import { Button, Field, Form, HorizontalGroup, InputControl, TextArea, usePanelContext, useStyles2 } from '@grafana/ui'; import { TagFilter } from 'app/core/components/TagFilter/TagFilter'; import { getAnnotationTags } from 'app/features/annotations/api'; diff --git a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx index c7c8cc19a01..dd354bf4df4 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationMarker.tsx @@ -1,11 +1,14 @@ +import { css } from '@emotion/css'; import React, { HTMLAttributes, useCallback, useRef, useState } from 'react'; +import { usePopper } from 'react-popper'; + import { GrafanaTheme2, dateTimeFormat, systemDateFormats, TimeZone } from '@grafana/data'; import { Portal, useStyles2, usePanelContext } from '@grafana/ui'; -import { css } from '@emotion/css'; -import { AnnotationEditorForm } from './AnnotationEditorForm'; -import { getCommonAnnotationStyles } from '../styles'; -import { usePopper } from 'react-popper'; import { getTooltipContainerStyles } from '@grafana/ui/src/themes/mixins'; + +import { getCommonAnnotationStyles } from '../styles'; + +import { AnnotationEditorForm } from './AnnotationEditorForm'; import { AnnotationTooltip } from './AnnotationTooltip'; interface Props extends HTMLAttributes { diff --git a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationTooltip.tsx b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationTooltip.tsx index ce5ff0ba437..0a6da533d90 100644 --- a/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationTooltip.tsx +++ b/public/app/plugins/panel/timeseries/plugins/annotations/AnnotationTooltip.tsx @@ -1,11 +1,11 @@ -import React from 'react'; import { css } from '@emotion/css'; -import { HorizontalGroup, IconButton, Tag, useStyles2 } from '@grafana/ui'; -import { GrafanaTheme2, textUtil } from '@grafana/data'; +import React from 'react'; +import { GrafanaTheme2, textUtil } from '@grafana/data'; +import { HorizontalGroup, IconButton, Tag, useStyles2 } from '@grafana/ui'; +import config from 'app/core/config'; import alertDef from 'app/features/alerting/state/alertDef'; import { CommentManager } from 'app/features/comments/CommentManager'; -import config from 'app/core/config'; interface AnnotationTooltipProps { annotation: AnnotationsDataFrameViewDTO; diff --git a/public/app/plugins/panel/timeseries/plugins/styles.ts b/public/app/plugins/panel/timeseries/plugins/styles.ts index 31dc2fc6aae..9bb7054dfae 100644 --- a/public/app/plugins/panel/timeseries/plugins/styles.ts +++ b/public/app/plugins/panel/timeseries/plugins/styles.ts @@ -1,5 +1,6 @@ -import { GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; export const getCommonAnnotationStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/plugins/panel/timeseries/suggestions.ts b/public/app/plugins/panel/timeseries/suggestions.ts index 72d1c020c11..e1e3906a912 100644 --- a/public/app/plugins/panel/timeseries/suggestions.ts +++ b/public/app/plugins/panel/timeseries/suggestions.ts @@ -8,6 +8,7 @@ import { StackingMode, } from '@grafana/schema'; import { SuggestionName } from 'app/types/suggestions'; + import { TimeSeriesOptions } from './types'; export class TimeSeriesSuggestionsSupplier { diff --git a/public/app/plugins/panel/timeseries/utils.test.ts b/public/app/plugins/panel/timeseries/utils.test.ts index 0d2065a8bff..3064163f1b2 100644 --- a/public/app/plugins/panel/timeseries/utils.test.ts +++ b/public/app/plugins/panel/timeseries/utils.test.ts @@ -1,4 +1,5 @@ import { createTheme, FieldType, MutableDataFrame, toDataFrame } from '@grafana/data'; + import { prepareGraphableFields } from './utils'; describe('prepare timeseries graph', () => { diff --git a/public/app/plugins/panel/welcome/Welcome.tsx b/public/app/plugins/panel/welcome/Welcome.tsx index d2194971b8e..fa623ef0e8e 100644 --- a/public/app/plugins/panel/welcome/Welcome.tsx +++ b/public/app/plugins/panel/welcome/Welcome.tsx @@ -1,5 +1,6 @@ -import React, { FC } from 'react'; import { css } from '@emotion/css'; +import React, { FC } from 'react'; + import { GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme } from '@grafana/ui'; diff --git a/public/app/plugins/panel/welcome/module.ts b/public/app/plugins/panel/welcome/module.ts index d7b86178a29..4a654deafb1 100644 --- a/public/app/plugins/panel/welcome/module.ts +++ b/public/app/plugins/panel/welcome/module.ts @@ -1,4 +1,5 @@ import { PanelPlugin } from '@grafana/data'; + import { WelcomeBanner } from './Welcome'; export const plugin = new PanelPlugin(WelcomeBanner).setNoPadding(); diff --git a/public/app/plugins/panel/xychart/ExplicitEditor.tsx b/public/app/plugins/panel/xychart/ExplicitEditor.tsx index a5af738005f..d2ddac4d6da 100644 --- a/public/app/plugins/panel/xychart/ExplicitEditor.tsx +++ b/public/app/plugins/panel/xychart/ExplicitEditor.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; + import { StandardEditorProps } from '@grafana/data'; import { XYChartOptions, ScatterFieldConfig } from './models.gen'; diff --git a/public/app/plugins/panel/xychart/TooltipView.tsx b/public/app/plugins/panel/xychart/TooltipView.tsx index db62263a678..d5fa55c0084 100644 --- a/public/app/plugins/panel/xychart/TooltipView.tsx +++ b/public/app/plugins/panel/xychart/TooltipView.tsx @@ -1,8 +1,10 @@ -import React, { PureComponent } from 'react'; -import { stylesFactory } from '@grafana/ui'; -import { DataFrame, Field, formattedValueToString, getFieldDisplayName, GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + +import { DataFrame, Field, formattedValueToString, getFieldDisplayName, GrafanaTheme2 } from '@grafana/data'; +import { stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; + import { ScatterSeries } from './types'; export interface Props { diff --git a/public/app/plugins/panel/xychart/XYChartPanel2.tsx b/public/app/plugins/panel/xychart/XYChartPanel2.tsx index dd1051fa648..262c9aa74a0 100644 --- a/public/app/plugins/panel/xychart/XYChartPanel2.tsx +++ b/public/app/plugins/panel/xychart/XYChartPanel2.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; + import { PanelProps } from '@grafana/data'; -import { XYChartOptions } from './models.gen'; -import { ScatterHoverEvent, ScatterSeries } from './types'; +import { config } from '@grafana/runtime'; import { LegendDisplayMode, Portal, @@ -13,9 +13,11 @@ import { VizTooltipContainer, } from '@grafana/ui'; import { FacetedData } from '@grafana/ui/src/components/uPlot/types'; -import { prepData, prepScatter } from './scatter'; -import { config } from '@grafana/runtime'; + import { TooltipView } from './TooltipView'; +import { XYChartOptions } from './models.gen'; +import { prepData, prepScatter } from './scatter'; +import { ScatterHoverEvent, ScatterSeries } from './types'; type Props = PanelProps; type State = { diff --git a/public/app/plugins/panel/xychart/XYDimsEditor.tsx b/public/app/plugins/panel/xychart/XYDimsEditor.tsx index 50019f6354e..040a0dbb0a5 100644 --- a/public/app/plugins/panel/xychart/XYDimsEditor.tsx +++ b/public/app/plugins/panel/xychart/XYDimsEditor.tsx @@ -1,6 +1,6 @@ -import React, { FC, useMemo } from 'react'; import { css } from '@emotion/css'; -import { IconButton, Label, Select, stylesFactory, useTheme } from '@grafana/ui'; +import React, { FC, useMemo } from 'react'; + import { SelectableValue, getFrameDisplayName, @@ -8,9 +8,10 @@ import { StandardEditorProps, getFieldDisplayName, } from '@grafana/data'; +import { IconButton, Label, Select, stylesFactory, useTheme } from '@grafana/ui'; -import { XYDimensionConfig, XYChartOptions } from './models.gen'; import { getXYDimensions, isGraphable } from './dims'; +import { XYDimensionConfig, XYChartOptions } from './models.gen'; interface XYInfo { numberFields: Array>; diff --git a/public/app/plugins/panel/xychart/config.ts b/public/app/plugins/panel/xychart/config.ts index 5d5347a2344..ac05945f02f 100644 --- a/public/app/plugins/panel/xychart/config.ts +++ b/public/app/plugins/panel/xychart/config.ts @@ -6,9 +6,10 @@ import { SetFieldConfigOptionsArgs, } from '@grafana/data'; import { LineStyle, VisibilityMode } from '@grafana/schema'; - import { commonOptionsBuilder, graphFieldOptions } from '@grafana/ui'; + import { LineStyleEditor } from '../timeseries/LineStyleEditor'; + import { ScatterFieldConfig, ScatterLineMode } from './models.gen'; const categoryStyles = undefined; // ['Scatter styles']; diff --git a/public/app/plugins/panel/xychart/dims.ts b/public/app/plugins/panel/xychart/dims.ts index 7f11ee1757d..b2228c63823 100644 --- a/public/app/plugins/panel/xychart/dims.ts +++ b/public/app/plugins/panel/xychart/dims.ts @@ -1,8 +1,9 @@ import { DataFrame, Field, FieldMatcher, FieldType, getFieldDisplayName } from '@grafana/data'; +import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types'; + import { XYDimensionConfig } from './models.gen'; // TODO: fix import -import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types'; export enum DimensionError { NoData, diff --git a/public/app/plugins/panel/xychart/module.tsx b/public/app/plugins/panel/xychart/module.tsx index 32e917025ba..d9a46da727f 100644 --- a/public/app/plugins/panel/xychart/module.tsx +++ b/public/app/plugins/panel/xychart/module.tsx @@ -1,11 +1,12 @@ import { PanelPlugin } from '@grafana/data'; import { commonOptionsBuilder } from '@grafana/ui'; -import { defaultScatterConfig, XYChartOptions, ScatterFieldConfig } from './models.gen'; -import { getScatterFieldConfig } from './config'; -import { XYDimsEditor } from './XYDimsEditor'; -import { XYChartPanel2 } from './XYChartPanel2'; import { ColorDimensionEditor, ScaleDimensionEditor } from 'app/features/dimensions/editors'; +import { XYChartPanel2 } from './XYChartPanel2'; +import { XYDimsEditor } from './XYDimsEditor'; +import { getScatterFieldConfig } from './config'; +import { defaultScatterConfig, XYChartOptions, ScatterFieldConfig } from './models.gen'; + export const plugin = new PanelPlugin(XYChartPanel2) .useFieldConfig(getScatterFieldConfig(defaultScatterConfig)) .setPanelOptions((builder) => { diff --git a/public/app/plugins/panel/xychart/scatter.ts b/public/app/plugins/panel/xychart/scatter.ts index 5830c75b414..022e9db6707 100644 --- a/public/app/plugins/panel/xychart/scatter.ts +++ b/public/app/plugins/panel/xychart/scatter.ts @@ -1,3 +1,5 @@ +import uPlot from 'uplot'; + import { DataFrame, FieldColorModeId, @@ -8,6 +10,8 @@ import { getFieldSeriesColor, GrafanaTheme2, } from '@grafana/data'; +import { alpha } from '@grafana/data/src/themes/colorManipulator'; +import { config } from '@grafana/runtime'; import { AxisPlacement, ScaleDirection, ScaleOrientation, VisibilityMode } from '@grafana/schema'; import { UPlotConfigBuilder } from '@grafana/ui'; import { FacetedData, FacetSeries } from '@grafana/ui/src/components/uPlot/types'; @@ -17,13 +21,12 @@ import { ScaleDimensionConfig, ScaleDimensionMode, } from 'app/features/dimensions'; -import { config } from '@grafana/runtime'; -import { defaultScatterConfig, ScatterFieldConfig, ScatterLineMode, XYChartOptions } from './models.gen'; + import { pointWithin, Quadtree, Rect } from '../barchart/quadtree'; -import { alpha } from '@grafana/data/src/themes/colorManipulator'; -import uPlot from 'uplot'; -import { DimensionValues, ScatterHoverCallback, ScatterSeries } from './types'; + import { isGraphable } from './dims'; +import { defaultScatterConfig, ScatterFieldConfig, ScatterLineMode, XYChartOptions } from './models.gen'; +import { DimensionValues, ScatterHoverCallback, ScatterSeries } from './types'; export interface ScatterPanelInfo { error?: string; diff --git a/public/app/plugins/panel/xychart/types.ts b/public/app/plugins/panel/xychart/types.ts index 10e6e64171a..0ef28147778 100644 --- a/public/app/plugins/panel/xychart/types.ts +++ b/public/app/plugins/panel/xychart/types.ts @@ -2,6 +2,7 @@ import { DataFrame, Field, FieldColorMode } from '@grafana/data'; import { LineStyle, VisibilityMode } from '@grafana/schema'; import { VizLegendItem } from '@grafana/ui'; import { ScaleDimensionConfig } from 'app/features/dimensions'; + import { ScatterLineMode } from './models.gen'; /** diff --git a/public/app/plugins/sdk.ts b/public/app/plugins/sdk.ts index 58fd3f2b02f..c70ba4638ad 100644 --- a/public/app/plugins/sdk.ts +++ b/public/app/plugins/sdk.ts @@ -1,7 +1,7 @@ import { makeClassES5Compatible } from '@grafana/data'; import { loadPluginCss } from '@grafana/runtime'; -import { PanelCtrl as PanelCtrlES6 } from 'app/angular/panel/panel_ctrl'; import { MetricsPanelCtrl as MetricsPanelCtrlES6 } from 'app/angular/panel/metrics_panel_ctrl'; +import { PanelCtrl as PanelCtrlES6 } from 'app/angular/panel/panel_ctrl'; import { QueryCtrl as QueryCtrlES6 } from 'app/angular/panel/query_ctrl'; const PanelCtrl = makeClassES5Compatible(PanelCtrlES6); diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index b749467036d..ecefa48d15f 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -1,19 +1,21 @@ import React from 'react'; -import LdapPage from 'app/features/admin/ldap/LdapPage'; -import UserAdminPage from 'app/features/admin/UserAdminPage'; +import { Redirect } from 'react-router-dom'; + +import ErrorPage from 'app/core/components/ErrorPage/ErrorPage'; import { LoginPage } from 'app/core/components/Login/LoginPage'; import config from 'app/core/config'; -import { AccessControlAction, DashboardRoutes } from 'app/types'; -import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamicImport'; -import { RouteDescriptor } from '../core/navigation/types'; -import { Redirect } from 'react-router-dom'; -import ErrorPage from 'app/core/components/ErrorPage/ErrorPage'; -import { getRoutes as getPluginCatalogRoutes } from 'app/features/plugins/admin/routes'; import { contextSrv } from 'app/core/services/context_srv'; -import { getLiveRoutes } from 'app/features/live/pages/routes'; +import UserAdminPage from 'app/features/admin/UserAdminPage'; +import LdapPage from 'app/features/admin/ldap/LdapPage'; import { getAlertingRoutes } from 'app/features/alerting/routes'; +import { getLiveRoutes } from 'app/features/live/pages/routes'; +import { getRoutes as getPluginCatalogRoutes } from 'app/features/plugins/admin/routes'; import { getProfileRoutes } from 'app/features/profile/routes'; import { ServiceAccountPage } from 'app/features/serviceaccounts/ServiceAccountPage'; +import { AccessControlAction, DashboardRoutes } from 'app/types'; + +import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamicImport'; +import { RouteDescriptor } from '../core/navigation/types'; export const extraRoutes: RouteDescriptor[] = []; @@ -161,7 +163,11 @@ export function getAppRoutes(): RouteDescriptor[] { () => (config.viewersCanEdit ? [] : ['Editor', 'Admin']), [AccessControlAction.DataSourcesExplore] ), - component: SafeDynamicImport(() => import(/* webpackChunkName: "explore" */ 'app/features/explore/Wrapper')), + component: SafeDynamicImport(() => + config.exploreEnabled + ? import(/* webpackChunkName: "explore" */ 'app/features/explore/Wrapper') + : import(/* webpackChunkName: "explore-feature-toggle-page" */ 'app/features/explore/FeatureTogglePage') + ), }, { path: '/a/:pluginId/', @@ -418,6 +424,12 @@ export function getAppRoutes(): RouteDescriptor[] { () => import(/* webpackChunkName: "LibraryPanelsPage"*/ 'app/features/library-panels/LibraryPanelsPage') ), }, + { + path: '/notifications', + component: SafeDynamicImport( + () => import(/* webpackChunkName: "NotificationsPage"*/ 'app/features/notifications/NotificationsPage') + ), + }, ...getPluginCatalogRoutes(), ...getLiveRoutes(), ...getAlertingRoutes(), diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 03275629e9b..c28a2c0a6bb 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -1,11 +1,14 @@ import { configureStore as reduxConfigureStore, MiddlewareArray } from '@reduxjs/toolkit'; -import { setStore } from './store'; -import { StoreState } from 'app/types/store'; -import { addReducer, createRootReducer } from '../core/reducers/root'; -import { buildInitialState } from '../core/reducers/navModel'; import { AnyAction } from 'redux'; import { ThunkMiddleware } from 'redux-thunk'; +import { StoreState } from 'app/types/store'; + +import { buildInitialState } from '../core/reducers/navModel'; +import { addReducer, createRootReducer } from '../core/reducers/root'; + +import { setStore } from './store'; + export function addRootReducer(reducers: any) { // this is ok now because we add reducers before configureStore is called // in the future if we want to add reducers during runtime diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts index 969985ba21a..12d2341559f 100644 --- a/public/app/types/accessControl.ts +++ b/public/app/types/accessControl.ts @@ -110,6 +110,8 @@ export enum AccessControlAction { AlertingNotificationsExternalRead = 'alert.notifications.external:read', ActionAPIKeysRead = 'apikeys:read', + ActionAPIKeysCreate = 'apikeys:create', + ActionAPIKeysDelete = 'apikeys:delete', } export interface Role { diff --git a/public/app/types/appEvent.ts b/public/app/types/appEvent.ts index 940c2000ed8..b5ee46d553d 100644 --- a/public/app/types/appEvent.ts +++ b/public/app/types/appEvent.ts @@ -1,4 +1,5 @@ import { IAngularEvent } from 'angular'; + import { AppEvent } from '@grafana/data'; export interface AppEventEmitter { diff --git a/public/app/types/appNotifications.ts b/public/app/types/appNotifications.ts index 7976b447986..a3a7ea2aed7 100644 --- a/public/app/types/appNotifications.ts +++ b/public/app/types/appNotifications.ts @@ -4,8 +4,10 @@ export interface AppNotification { icon: string; title: string; text: string; + traceId?: string; component?: React.ReactElement; - timeout: AppNotificationTimeout; + showing: boolean; + timestamp: number; } export enum AppNotificationSeverity { @@ -16,11 +18,19 @@ export enum AppNotificationSeverity { } export enum AppNotificationTimeout { - Warning = 5000, Success = 3000, + Warning = 5000, Error = 7000, } +export const timeoutMap = { + [AppNotificationSeverity.Success]: AppNotificationTimeout.Success, + [AppNotificationSeverity.Warning]: AppNotificationTimeout.Warning, + [AppNotificationSeverity.Error]: AppNotificationTimeout.Error, + [AppNotificationSeverity.Info]: AppNotificationTimeout.Success, +}; + export interface AppNotificationsState { byId: Record; + lastRead: number; } diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index 614c6061663..68ace717636 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -1,8 +1,9 @@ -import { DashboardAcl } from './acl'; import { DataQuery } from '@grafana/data'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { VariableModel } from 'app/features/variables/types'; +import { DashboardAcl } from './acl'; + export interface DashboardDTO { redirectUri?: string; dashboard: DashboardDataDTO; diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 03eb07a1652..10bd4f0471a 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -1,6 +1,6 @@ import { DataSourcePluginMeta, DataSourceSettings, LayoutMode } from '@grafana/data'; -import { GenericDataSourcePlugin } from 'app/features/datasources/settings/PluginSettings'; import { HealthCheckResultDetails } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import { GenericDataSourcePlugin } from 'app/features/datasources/settings/PluginSettings'; export interface DataSourcesState { dataSources: DataSourceSettings[]; diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index ca313a28f06..5f836805e72 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -1,4 +1,5 @@ import { Observable, SubscriptionLike, Unsubscribable } from 'rxjs'; + import { AbsoluteTimeRange, DataFrame, diff --git a/public/app/types/serviceaccount.ts b/public/app/types/serviceaccount.ts index 40c4b533676..3448e99b546 100644 --- a/public/app/types/serviceaccount.ts +++ b/public/app/types/serviceaccount.ts @@ -1,4 +1,5 @@ import { SelectableValue, WithAccessControlMetadata } from '@grafana/data'; + import { ApiKey, OrgRole, Role } from '.'; export interface OrgServiceAccount extends WithAccessControlMetadata { diff --git a/public/app/types/store.ts b/public/app/types/store.ts index 7b03dc2b890..0cb1961c674 100644 --- a/public/app/types/store.ts +++ b/public/app/types/store.ts @@ -1,9 +1,3 @@ -import { ThunkAction, ThunkDispatch as GenericThunkDispatch } from 'redux-thunk'; -import { - useSelector as useSelectorUntyped, - TypedUseSelectorHook, - useDispatch as useDispatchUntyped, -} from 'react-redux'; import { Action, AsyncThunk, @@ -12,6 +6,13 @@ import { createAsyncThunk as createAsyncThunkUntyped, PayloadAction, } from '@reduxjs/toolkit'; +import { + useSelector as useSelectorUntyped, + TypedUseSelectorHook, + useDispatch as useDispatchUntyped, +} from 'react-redux'; +import { ThunkAction, ThunkDispatch as GenericThunkDispatch } from 'redux-thunk'; + import type { createRootReducer } from 'app/core/reducers/root'; import { configureStore } from 'app/store/configureStore'; diff --git a/public/app/types/teams.ts b/public/app/types/teams.ts index 92c83b436a9..99739affb95 100644 --- a/public/app/types/teams.ts +++ b/public/app/types/teams.ts @@ -1,4 +1,5 @@ import { WithAccessControlMetadata } from '@grafana/data'; + import { TeamPermissionLevel } from './acl'; export interface Team extends WithAccessControlMetadata { diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index 52b2e903e54..5c98037378e 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -1,6 +1,7 @@ /* Prometheus internal models */ import { AlertState, DataSourceInstanceSettings } from '@grafana/data'; + import { PromAlertingRuleState, PromRuleType, diff --git a/public/app/types/user.ts b/public/app/types/user.ts index 157bc143c0b..7d6eeab849d 100644 --- a/public/app/types/user.ts +++ b/public/app/types/user.ts @@ -1,5 +1,6 @@ -import { OrgRole } from '.'; import { SelectableValue, WithAccessControlMetadata } from '@grafana/data'; + +import { OrgRole } from '.'; export interface OrgUser extends WithAccessControlMetadata { avatarUrl: string; email: string; diff --git a/public/test/core/redux/reducerTester.test.ts b/public/test/core/redux/reducerTester.test.ts index f5cb8890897..fb4c71922e3 100644 --- a/public/test/core/redux/reducerTester.test.ts +++ b/public/test/core/redux/reducerTester.test.ts @@ -1,5 +1,5 @@ -import { AnyAction } from 'redux'; import { createAction } from '@reduxjs/toolkit'; +import { AnyAction } from 'redux'; import { reducerTester } from './reducerTester'; diff --git a/public/test/core/redux/reducerTester.ts b/public/test/core/redux/reducerTester.ts index 728a4aa9869..49c7dad1854 100644 --- a/public/test/core/redux/reducerTester.ts +++ b/public/test/core/redux/reducerTester.ts @@ -1,6 +1,6 @@ -import { Action } from 'redux'; import { AnyAction } from '@reduxjs/toolkit'; import { cloneDeep } from 'lodash'; +import { Action } from 'redux'; type GrafanaReducer = (state: S, action: A) => S; diff --git a/public/test/core/redux/reduxTester.ts b/public/test/core/redux/reduxTester.ts index c730b6f96a6..d3decce2c8e 100644 --- a/public/test/core/redux/reduxTester.ts +++ b/public/test/core/redux/reduxTester.ts @@ -1,5 +1,3 @@ -import { Dispatch, Middleware, MiddlewareAPI } from 'redux'; -import thunk, { ThunkMiddleware } from 'redux-thunk'; import { AnyAction, configureStore, @@ -9,10 +7,12 @@ import { CombinedState, PreloadedState, } from '@reduxjs/toolkit'; - -import { StoreState } from '../../../app/types'; -import { setStore } from '../../../app/store/store'; import { NoInfer } from '@reduxjs/toolkit/dist/tsHelpers'; +import { Dispatch, Middleware, MiddlewareAPI } from 'redux'; +import thunk, { ThunkMiddleware } from 'redux-thunk'; + +import { setStore } from '../../../app/store/store'; +import { StoreState } from '../../../app/types'; export interface ReduxTesterGiven { givenRootReducer: (rootReducer: Reducer) => ReduxTesterWhen; diff --git a/public/test/core/thunk/thunkTester.ts b/public/test/core/thunk/thunkTester.ts index 944d44fd129..8afedcb53bf 100644 --- a/public/test/core/thunk/thunkTester.ts +++ b/public/test/core/thunk/thunkTester.ts @@ -1,7 +1,7 @@ // @ts-ignore +import { PayloadAction } from '@reduxjs/toolkit'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import { PayloadAction } from '@reduxjs/toolkit'; const mockStore = configureMockStore([thunk]); diff --git a/public/test/global-jquery-shim.ts b/public/test/global-jquery-shim.ts new file mode 100644 index 00000000000..645b39e9c63 --- /dev/null +++ b/public/test/global-jquery-shim.ts @@ -0,0 +1,4 @@ +import $ from 'jquery'; + +const global = window as any; +global.$ = global.jQuery = $; diff --git a/public/test/helpers/TestProvider.tsx b/public/test/helpers/TestProvider.tsx index cc93f37db33..ba71a620dfa 100644 --- a/public/test/helpers/TestProvider.tsx +++ b/public/test/helpers/TestProvider.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { I18nProvider } from '../../app/core/localisation'; const TestProvider: React.FC = ({ children }) => { diff --git a/public/test/helpers/convertToStoreState.ts b/public/test/helpers/convertToStoreState.ts index 40bbe568e91..8a93487ae02 100644 --- a/public/test/helpers/convertToStoreState.ts +++ b/public/test/helpers/convertToStoreState.ts @@ -1,5 +1,5 @@ -import { StoreState } from '../../app/types'; import { getPreloadedState } from '../../app/features/variables/state/helpers'; +import { StoreState } from '../../app/types'; export const convertToStoreState = (key: string, models: any[]): StoreState => { const variables = models.reduce((byName, variable) => { diff --git a/public/test/helpers/getTemplateSrvDependencies.ts b/public/test/helpers/getTemplateSrvDependencies.ts index 6c52f78c9e4..c1da0c4d247 100644 --- a/public/test/helpers/getTemplateSrvDependencies.ts +++ b/public/test/helpers/getTemplateSrvDependencies.ts @@ -1,6 +1,7 @@ +import { TemplateSrvDependencies } from 'app/features/templating/template_srv'; + import { getFilteredVariables, getVariables, getVariableWithName } from '../../app/features/variables/state/selectors'; import { StoreState } from '../../app/types'; -import { TemplateSrvDependencies } from 'app/features/templating/template_srv'; export const getTemplateSrvDependencies = (state: StoreState): TemplateSrvDependencies => ({ getFilteredVariables: (filter) => getFilteredVariables(filter, state), diff --git a/public/test/helpers/initTemplateSrv.ts b/public/test/helpers/initTemplateSrv.ts index a19688f282c..acf5b026a7d 100644 --- a/public/test/helpers/initTemplateSrv.ts +++ b/public/test/helpers/initTemplateSrv.ts @@ -1,6 +1,8 @@ import { TimeRange } from '@grafana/data'; -import { convertToStoreState } from './convertToStoreState'; + import { TemplateSrv } from '../../app/features/templating/template_srv'; + +import { convertToStoreState } from './convertToStoreState'; import { getTemplateSrvDependencies } from './getTemplateSrvDependencies'; export function initTemplateSrv(key: string, variables: any[], timeRange?: TimeRange) { diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index 40a7a0ceffa..de839539c09 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -1,10 +1,18 @@ -import { configure } from 'enzyme'; -import { EventBusSrv } from '@grafana/data'; +// This import has side effects, and must be at the top so jQuery is made global before +// angular is imported. +import './global-jquery-shim'; + import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; -import $ from 'jquery'; +import angular from 'angular'; +import { configure } from 'enzyme'; + +import { EventBusSrv } from '@grafana/data'; import 'mutationobserver-shim'; import './mocks/workers'; +import '../vendor/flot/jquery.flot'; +import '../vendor/flot/jquery.flot.time'; + const testAppEvents = new EventBusSrv(); const global = window as any; global.$ = global.jQuery = $; @@ -24,10 +32,6 @@ Object.defineProperty(global, 'matchMedia', { })), }); -import '../vendor/flot/jquery.flot'; -import '../vendor/flot/jquery.flot.time'; -import angular from 'angular'; - angular.module('grafana', ['ngRoute']); angular.module('grafana.services', ['ngRoute', '$strap.directives']); angular.module('grafana.panels', []); diff --git a/public/test/matchers/index.ts b/public/test/matchers/index.ts index 3b955e37493..b6f8a091aaa 100644 --- a/public/test/matchers/index.ts +++ b/public/test/matchers/index.ts @@ -1,7 +1,8 @@ -import { ObservableMatchers } from './types'; +import { Observable } from 'rxjs'; + import { toEmitValues } from './toEmitValues'; import { toEmitValuesWith } from './toEmitValuesWith'; -import { Observable } from 'rxjs'; +import { ObservableMatchers } from './types'; export const matchers: ObservableMatchers> = { toEmitValues, diff --git a/public/test/matchers/toEmitValues.test.ts b/public/test/matchers/toEmitValues.test.ts index a6411a1fa96..71edff27e8d 100644 --- a/public/test/matchers/toEmitValues.test.ts +++ b/public/test/matchers/toEmitValues.test.ts @@ -1,5 +1,6 @@ import { interval, Observable, of, throwError } from 'rxjs'; import { map, mergeMap, take } from 'rxjs/operators'; + import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; describe('toEmitValues matcher', () => { diff --git a/public/test/matchers/toEmitValues.ts b/public/test/matchers/toEmitValues.ts index 1ecf554646e..e8bf9e80470 100644 --- a/public/test/matchers/toEmitValues.ts +++ b/public/test/matchers/toEmitValues.ts @@ -1,7 +1,8 @@ -import { Observable, Subscription } from 'rxjs'; import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; -import { expectObservable, forceObservableCompletion } from './utils'; import { isEqual } from 'lodash'; +import { Observable, Subscription } from 'rxjs'; + +import { expectObservable, forceObservableCompletion } from './utils'; function passMessage(received: any[], expected: any[]) { return `${matcherHint('.not.toEmitValues')} diff --git a/public/test/matchers/toEmitValuesWith.ts b/public/test/matchers/toEmitValuesWith.ts index cc464302337..0764e674b65 100644 --- a/public/test/matchers/toEmitValuesWith.ts +++ b/public/test/matchers/toEmitValuesWith.ts @@ -1,6 +1,7 @@ -import { Observable, Subscription } from 'rxjs'; -import { expectObservable, forceObservableCompletion } from './utils'; import { matcherHint, printReceived } from 'jest-matcher-utils'; +import { Observable, Subscription } from 'rxjs'; + +import { expectObservable, forceObservableCompletion } from './utils'; function tryExpectations(received: any[], expectations: (received: any[]) => void): jest.CustomMatcherResult { try { diff --git a/public/test/matchers/utils.ts b/public/test/matchers/utils.ts index b8b677cb752..ddba865e453 100644 --- a/public/test/matchers/utils.ts +++ b/public/test/matchers/utils.ts @@ -1,7 +1,8 @@ import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; -import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; import { asapScheduler, Subscription, timer, isObservable } from 'rxjs'; +import { OBSERVABLE_TEST_TIMEOUT_IN_MS } from './types'; + export function forceObservableCompletion(subscription: Subscription, resolve: (args: any) => void) { const timeoutObservable = timer(OBSERVABLE_TEST_TIMEOUT_IN_MS, asapScheduler); diff --git a/public/test/mocks/datasource_srv.ts b/public/test/mocks/datasource_srv.ts index 3b6eeb45b30..e6caeda6d0c 100644 --- a/public/test/mocks/datasource_srv.ts +++ b/public/test/mocks/datasource_srv.ts @@ -1,3 +1,5 @@ +import { Observable } from 'rxjs'; + import { DataQueryRequest, DataQueryResponse, @@ -7,7 +9,6 @@ import { DataSourceRef, getDataSourceUID, } from '@grafana/data'; -import { Observable } from 'rxjs'; export class DatasourceSrvMock { constructor(private defaultDS: DataSourceApi, private datasources: { [name: string]: DataSourceApi }) { diff --git a/public/test/mocks/linguiMacro.tsx b/public/test/mocks/linguiMacro.tsx index f3f5c344fed..955298f6b8c 100644 --- a/public/test/mocks/linguiMacro.tsx +++ b/public/test/mocks/linguiMacro.tsx @@ -1,6 +1,6 @@ -import React from 'react'; import { MessageDescriptor } from '@lingui/core'; import { Trans as OriginalTrans } from '@lingui/macro'; +import React from 'react'; export const Trans: typeof OriginalTrans = ({ id, children }) => { return <>{children ?? id}; diff --git a/public/test/redux-rtl.tsx b/public/test/redux-rtl.tsx index 0c8b33148ef..279df6a49a6 100644 --- a/public/test/redux-rtl.tsx +++ b/public/test/redux-rtl.tsx @@ -1,10 +1,12 @@ -import React from 'react'; -import { render as rtlRender } from '@testing-library/react'; import { AnyAction, configureStore } from '@reduxjs/toolkit'; import { ThunkMiddlewareFor } from '@reduxjs/toolkit/dist/getDefaultMiddleware'; +import { render as rtlRender } from '@testing-library/react'; +import React from 'react'; import { Provider } from 'react-redux'; + import { createRootReducer } from 'app/core/reducers/root'; import { StoreState } from 'app/types'; + import { mockNavModel } from './mocks/navModel'; function render( diff --git a/public/test/setupTests.ts b/public/test/setupTests.ts index 2d4eeaae51f..4c88026270a 100644 --- a/public/test/setupTests.ts +++ b/public/test/setupTests.ts @@ -1,5 +1,6 @@ import '@testing-library/jest-dom'; import failOnConsole from 'jest-fail-on-console'; + import { matchers } from './matchers'; failOnConsole({ diff --git a/public/test/specs/helpers.ts b/public/test/specs/helpers.ts index 1bb8a295729..0bddf53b9cd 100644 --- a/public/test/specs/helpers.ts +++ b/public/test/specs/helpers.ts @@ -1,10 +1,11 @@ import { each, template } from 'lodash'; -import config from 'app/core/config'; -import { angularMocks, sinon } from '../lib/common'; -import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { RawTimeRange, PanelPluginMeta, dateMath } from '@grafana/data'; import { GrafanaRootScope } from 'app/angular/GrafanaCtrl'; +import config from 'app/core/config'; +import { PanelModel } from 'app/features/dashboard/state/PanelModel'; + +import { angularMocks, sinon } from '../lib/common'; export function ControllerTestContext(this: any) { const self = this; diff --git a/scripts/build/ci-build/Dockerfile b/scripts/build/ci-build/Dockerfile index d1cfb26dcb7..3a8b7454685 100644 --- a/scripts/build/ci-build/Dockerfile +++ b/scripts/build/ci-build/Dockerfile @@ -110,7 +110,7 @@ RUN rm dockerize-linux-amd64-v${DOCKERIZE_VERSION}.tar.gz # Use old Debian (this has support into 2022) in order to ensure binary compatibility with older glibc's. FROM debian:stretch-20210208 -ENV GOVERSION=1.17.8 \ +ENV GOVERSION=1.17.9 \ PATH=/usr/local/go/bin:$PATH \ GOPATH=/go \ NODEVERSION=16.14.0-1nodesource1 \ @@ -147,8 +147,10 @@ RUN apt-get update && \ unzip && \ gem install -N fpm && \ ln -s /usr/bin/llvm-dsymutil-6.0 /usr/bin/dsymutil && \ - curl -fsL https://deb.nodesource.com/setup_16.x | bash - && \ - apt-get update && apt-get install -yq nodejs=${NODEVERSION} && \ + curl -fsS https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ + curl -O https://deb.nodesource.com/node_16.x/pool/main/n/nodejs/nodejs_${NODEVERSION}_amd64.deb &&\ + dpkg -i nodejs_${NODEVERSION}_amd64.deb &&\ + rm nodejs_${NODEVERSION}_amd64.deb &&\ curl -fsS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -yq yarn=${YARNVERSION} && \ diff --git a/scripts/build/ci-build/Makefile b/scripts/build/ci-build/Makefile deleted file mode 100644 index 5645595c348..00000000000 --- a/scripts/build/ci-build/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -VERSION = dev -TAG = grafana/build-container -USER_ID = $(shell id -u) -GROUP_ID = $(shell id -g) - -all: build deploy - -build: - docker build -t "${TAG}:${VERSION}" . - -deploy: - docker push "${TAG}:${VERSION}" - -run: - docker run -ti \ - -e "CIRCLE_BRANCH=local" \ - -e "CIRCLE_BUILD_NUM=472" \ - ${TAG}:${VERSION} \ - bash - -run-with-local-source-live: - docker run -d \ - -e "CIRCLE_BRANCH=local" \ - -e "CIRCLE_BUILD_NUM=472" \ - -w "/go/src/github.com/grafana/grafana" \ - --name grafana-build \ - -v "${GOPATH}/src/github.com/grafana/grafana:/go/src/github.com/grafana/grafana" \ - ${TAG}:${VERSION} \ - bash -c "/tmp/bootstrap.sh; mkdir /.cache; chown "${USER_ID}:${GROUP_ID}" /.cache; tail -f /dev/null" - docker exec -ti --user "${USER_ID}:${GROUP_ID}" grafana-build bash - -run-with-local-source-copy: - docker run -d \ - -e "CIRCLE_BRANCH=local" \ - -e "CIRCLE_BUILD_NUM=472" \ - -w "/go/src/github.com/grafana/grafana" \ - --name grafana-build \ - ${TAG}:${VERSION} \ - bash -c "/tmp/bootstrap.sh; tail -f /dev/null" - docker cp "${GOPATH}/src/github.com/grafana/grafana" grafana-build:/go/src/github.com/grafana/ - docker exec -ti grafana-build bash - -update-source: - docker cp "${GOPATH}/src/github.com/grafana/grafana" grafana-build:/go/src/github.com/grafana/ - -attach: - docker exec -ti grafana-build bash - -attach-live: - docker exec -ti --user "${USER_ID}:${GROUP_ID}" grafana-build bash - -stop: - docker kill grafana-build - docker rm grafana-build diff --git a/scripts/build/ci-build/README.md b/scripts/build/ci-build/README.md index 61a9695f7c2..94790a90a7c 100644 --- a/scripts/build/ci-build/README.md +++ b/scripts/build/ci-build/README.md @@ -5,19 +5,6 @@ is in Dockerfile, but there are supporting scripts such as the Makefile, for bui The image is based on Debian Stretch, since we want an older Linux distribution (Stretch has long-term support into 2022) to build binaries that are as portable as possible. -## Makefile targets - -- `make run-with-local-source-copy` - - Starts the container locally and copies your local sources into the container -- `make run-with-local-source-live` - - Starts the container (as your user) locally and maps your Grafana project dir into the container -- `make update-source` - - Updates the sources in the container from your local sources -- `make stop` - - Kills the container -- `make attach` - - Opens bash within the running container - ## Build/Publish Docker Image In order to build and publish the Grafana build Docker image, execute the following: diff --git a/scripts/cli/generateSassVariableFiles.ts b/scripts/cli/generateSassVariableFiles.ts index dad9c610e05..4e7cb99d5b7 100644 --- a/scripts/cli/generateSassVariableFiles.ts +++ b/scripts/cli/generateSassVariableFiles.ts @@ -1,8 +1,9 @@ import * as fs from 'fs'; + +import { createTheme } from '@grafana/data'; import { darkThemeVarsTemplate } from '@grafana/ui/src/themes/_variables.dark.scss.tmpl'; import { lightThemeVarsTemplate } from '@grafana/ui/src/themes/_variables.light.scss.tmpl'; import { commonThemeVarsTemplate } from '@grafana/ui/src/themes/_variables.scss.tmpl'; -import { createTheme } from '@grafana/data'; const darkThemeVariablesPath = __dirname + '/../../public/sass/_variables.dark.generated.scss'; const lightThemeVariablesPath = __dirname + '/../../public/sass/_variables.light.generated.scss'; diff --git a/scripts/drone/pipelines/release.star b/scripts/drone/pipelines/release.star index 24f21985882..f786ce34dbf 100644 --- a/scripts/drone/pipelines/release.star +++ b/scripts/drone/pipelines/release.star @@ -423,6 +423,7 @@ def publish_npm_pipelines(mode): } steps = [ download_grabpl_step(), + yarn_install_step(), retrieve_npm_packages_step(), release_npm_packages_step() ] diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 55c861d9968..02e3afe4873 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -1,7 +1,7 @@ load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token', 'prerelease_bucket') -grabpl_version = 'v2.9.37' -build_image = 'grafana/build-container:1.5.3' +grabpl_version = 'v2.9.41' +build_image = 'grafana/build-container:1.5.4' publish_image = 'grafana/grafana-ci-deploy:1.3.1' deploy_docker_image = 'us.gcr.io/kubernetes-dev/drone/plugins/deploy-image' alpine_image = 'alpine:3.15' diff --git a/scripts/webpack/dependencies.js b/scripts/webpack/dependencies.js index 8b253145e28..2aecd51d4e3 100644 --- a/scripts/webpack/dependencies.js +++ b/scripts/webpack/dependencies.js @@ -1,8 +1,9 @@ 'use strict'; -const pkg = require('../../package.json'); const { pull } = require('lodash'); +const pkg = require('../../package.json'); + let dependencies = Object.keys(pkg.dependencies); // remove jquery so we can add it first // remove rxjs so we can only depend on parts of it in code diff --git a/scripts/webpack/loaders/compile.js b/scripts/webpack/loaders/compile.js index 4c88ab473da..29f9ffe81fc 100644 --- a/scripts/webpack/loaders/compile.js +++ b/scripts/webpack/loaders/compile.js @@ -1,10 +1,9 @@ const loaderUtils = require('loader-utils'); - -const WebWorkerTemplatePlugin = require('webpack/lib/webworker/WebWorkerTemplatePlugin'); const ExternalsPlugin = require('webpack/lib/ExternalsPlugin'); -const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin'); const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); +const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); +const WebWorkerTemplatePlugin = require('webpack/lib/webworker/WebWorkerTemplatePlugin'); const COMPILATION_METADATA = Symbol('COMPILATION_METADATA'); diff --git a/scripts/webpack/sass.rule.js b/scripts/webpack/sass.rule.js index d68d6b35c56..36290bb3adc 100644 --- a/scripts/webpack/sass.rule.js +++ b/scripts/webpack/sass.rule.js @@ -1,7 +1,7 @@ 'use strict'; -const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const path = require('path'); module.exports = function (options) { return { diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index a8ac713dd8a..d8d00d6cc52 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -1,8 +1,9 @@ +const CopyWebpackPlugin = require('copy-webpack-plugin'); const fs = require('fs-extra'); const path = require('path'); const webpack = require('webpack'); + const CorsWorkerPlugin = require('./plugins/CorsWorkerPlugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); class CopyUniconsPlugin { apply(compiler) { diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index acdb133bc7d..dddb04ae409 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -1,13 +1,14 @@ 'use strict'; -const { merge } = require('webpack-merge'); -const common = require('./webpack.common.js'); -const path = require('path'); -const { DefinePlugin } = require('webpack'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); const ESLintPlugin = require('eslint-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const path = require('path'); +const { DefinePlugin } = require('webpack'); +const { merge } = require('webpack-merge'); + +const common = require('./webpack.common.js'); // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = (env = {}) => diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js index 2abb19043ec..41933f42758 100644 --- a/scripts/webpack/webpack.hot.js +++ b/scripts/webpack/webpack.hot.js @@ -1,12 +1,13 @@ 'use strict'; -const { merge } = require('webpack-merge'); -const common = require('./webpack.common.js'); -const path = require('path'); -const { DefinePlugin } = require('webpack'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const path = require('path'); +const { DefinePlugin } = require('webpack'); +const { merge } = require('webpack-merge'); + +const common = require('./webpack.common.js'); module.exports = merge(common, { devtool: 'inline-source-map', diff --git a/scripts/webpack/webpack.prod.js b/scripts/webpack/webpack.prod.js index 3595d2d34a8..ec9c1ac5759 100644 --- a/scripts/webpack/webpack.prod.js +++ b/scripts/webpack/webpack.prod.js @@ -1,12 +1,13 @@ 'use strict'; -const { merge } = require('webpack-merge'); -const TerserPlugin = require('terser-webpack-plugin'); -const common = require('./webpack.common.js'); -const path = require('path'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const path = require('path'); +const TerserPlugin = require('terser-webpack-plugin'); +const { merge } = require('webpack-merge'); + +const common = require('./webpack.common.js'); module.exports = (env = {}) => merge(common, { diff --git a/yarn.lock b/yarn.lock index 5273232fb8d..89c0142448e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3997,7 +3997,7 @@ __metadata: "@testing-library/jest-dom": 5.16.2 "@testing-library/react": 12.1.4 "@testing-library/react-hooks": 7.0.2 - "@testing-library/user-event": 13.5.0 + "@testing-library/user-event": 14.1.1 "@types/d3-interpolate": ^1.4.0 "@types/history": 4.7.11 "@types/jest": 27.4.1 @@ -4135,9 +4135,9 @@ __metadata: languageName: node linkType: hard -"@grafana/experimental@npm:0.0.2-canary.22": - version: 0.0.2-canary.22 - resolution: "@grafana/experimental@npm:0.0.2-canary.22" +"@grafana/experimental@npm:^0.0.2-canary.25": + version: 0.0.2-canary.25 + resolution: "@grafana/experimental@npm:0.0.2-canary.25" dependencies: "@types/uuid": ^8.3.3 uuid: ^8.3.2 @@ -4145,7 +4145,7 @@ __metadata: "@emotion/css": 11.1.3 react: 17.0.1 react-select: 5.2.1 - checksum: b9a64c0abc33798967c94e82e329925f75661eb23b4bbaf4d34fc0c95db1a535b95a240deb6e95fe08f2a2207859e599daf51b560813cf0e5c85468fa6d7a5cc + checksum: 20532d6a1ff1bb7a98db71728bc34474f87663431fef349a53f4673d12b6356336b9250bc85028693e7bbfeec8e468d9910837f4062cb49239d581dc974c55c9 languageName: node linkType: hard @@ -4180,7 +4180,7 @@ __metadata: "@sentry/browser": 6.19.1 "@testing-library/dom": 8.13.0 "@testing-library/react": 12.1.4 - "@testing-library/user-event": ^13.5.0 + "@testing-library/user-event": 14.1.1 "@types/angular": 1.8.4 "@types/history": 4.7.11 "@types/jest": 27.4.1 @@ -4277,7 +4277,7 @@ __metadata: "@types/webpack": 4.41.7 "@typescript-eslint/eslint-plugin": 4.28.0 "@typescript-eslint/parser": 4.28.0 - axios: 0.21.2 + axios: 0.26.1 babel-jest: 26.6.3 babel-loader: 8.2.2 babel-plugin-angularjs-annotate: 0.10.0 @@ -4387,7 +4387,7 @@ __metadata: "@testing-library/jest-dom": 5.16.2 "@testing-library/react": 12.1.4 "@testing-library/react-hooks": 7.0.2 - "@testing-library/user-event": 13.5.0 + "@testing-library/user-event": 14.1.1 "@types/classnames": 2.3.0 "@types/common-tags": ^1.8.0 "@types/d3": 7.1.0 @@ -5180,15 +5180,15 @@ __metadata: languageName: node linkType: hard -"@kusto/monaco-kusto@npm:5.0.0": - version: 5.0.0 - resolution: "@kusto/monaco-kusto@npm:5.0.0" +"@kusto/monaco-kusto@npm:5.1.1": + version: 5.1.1 + resolution: "@kusto/monaco-kusto@npm:5.1.1" dependencies: "@kusto/language-service": 2.0.0-beta.0 "@kusto/language-service-next": 0.0.54 peerDependencies: monaco-editor: 0.33.0 - checksum: 961bcae8a84562ad0d1aa199bdacbe5adfffafb01e98a2cf927883331d45ad8fcdf7385f9bde00eed16f74a33e0dea41f9eb80127e2e5a341ea3b8a2aa9fb2d8 + checksum: 51771a0a128640d6385d693200ef1128fa2d55250e4f8db97aa4e9c46350dc94c93238958a9a9640939c6813caad117fe3a858b50e7b72a413baa78e964a359c languageName: node linkType: hard @@ -6881,6 +6881,40 @@ __metadata: languageName: node linkType: hard +"@reach/observe-rect@npm:^1.1.0": + version: 1.2.0 + resolution: "@reach/observe-rect@npm:1.2.0" + checksum: 7dd903eeaad0e22c6d973bd26265d91eadba56ab5134701ceb3e85214db75339fae94aa7e8b88a65e8daa64bc7cf1b915d4ffcdfd324466b561dc6adc3c6e070 + languageName: node + linkType: hard + +"@reach/portal@npm:^0.16.0": + version: 0.16.2 + resolution: "@reach/portal@npm:0.16.2" + dependencies: + "@reach/utils": 0.16.0 + tiny-warning: ^1.0.3 + tslib: ^2.3.0 + peerDependencies: + react: ^16.8.0 || 17.x + react-dom: ^16.8.0 || 17.x + checksum: 7413dcd169cfb9854dd0d3a01f811ec19ef170558fcbd00118d676fc02c197d1c0bce1d1357508879d4775169561d103e13a8e4d74cc677eb0037cc7b04f7a1e + languageName: node + linkType: hard + +"@reach/utils@npm:0.16.0": + version: 0.16.0 + resolution: "@reach/utils@npm:0.16.0" + dependencies: + tiny-warning: ^1.0.3 + tslib: ^2.3.0 + peerDependencies: + react: ^16.8.0 || 17.x + react-dom: ^16.8.0 || 17.x + checksum: 36bc0eb41a71798eb6186b23de265ba709e51dae5bf214fb8505c66bb3f2e6a41bb2401457350436ba89ca9e3a50f93a04fe7c33d15648ce11e568a85622d770 + languageName: node + linkType: hard + "@react-aria/button@npm:3.4.3": version: 3.4.3 resolution: "@react-aria/button@npm:3.4.3" @@ -9030,7 +9064,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:8.13.0": +"@testing-library/dom@npm:8.13.0, @testing-library/dom@npm:>=7, @testing-library/dom@npm:^8.0.0": version: 8.13.0 resolution: "@testing-library/dom@npm:8.13.0" dependencies: @@ -9046,22 +9080,6 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:>=7, @testing-library/dom@npm:^8.0.0": - version: 8.10.1 - resolution: "@testing-library/dom@npm:8.10.1" - dependencies: - "@babel/code-frame": ^7.10.4 - "@babel/runtime": ^7.12.5 - "@types/aria-query": ^4.2.0 - aria-query: ^5.0.0 - chalk: ^4.1.0 - dom-accessibility-api: ^0.5.9 - lz-string: ^1.4.4 - pretty-format: ^27.0.2 - checksum: c11da16d981d4479fa18b2e90a62d2c19871da414016a4f80136394cf35e833ca4de4b56a64225f6bd5d3a13842176236d3508d217e3858c5e2ef35a3f391d5b - languageName: node - linkType: hard - "@testing-library/jest-dom@npm:5.16.2": version: 5.16.2 resolution: "@testing-library/jest-dom@npm:5.16.2" @@ -9115,14 +9133,12 @@ __metadata: languageName: node linkType: hard -"@testing-library/user-event@npm:13.5.0, @testing-library/user-event@npm:^13.5.0": - version: 13.5.0 - resolution: "@testing-library/user-event@npm:13.5.0" - dependencies: - "@babel/runtime": ^7.12.5 +"@testing-library/user-event@npm:14.1.1": + version: 14.1.1 + resolution: "@testing-library/user-event@npm:14.1.1" peerDependencies: "@testing-library/dom": ">=7.21.4" - checksum: 16319de685fbb7008f1ba667928f458b2d08196918002daca56996de80ef35e6d9de26e9e1ece7d00a004692b95a597cf9142fff0dc53f2f51606a776584f549 + checksum: dd02aecbde64002aa3c58392137672156101fc3bb821f43db99e3c652e6a6d34b3eb29b2524eb41ad2a317cfdff6ab2e88d7027605d88dccaacf4f07a08dc7a9 languageName: node linkType: hard @@ -10114,6 +10130,13 @@ __metadata: languageName: node linkType: hard +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: e60b153664572116dfea673c5bda7778dbff150498f44f998e34b5886d8afc47f16799280e4b6e241c0472aef1bc36add771c569c68fc5125fc2ae519a3eb9ac + languageName: node + linkType: hard + "@types/jsurl@npm:^1.2.28": version: 1.2.30 resolution: "@types/jsurl@npm:1.2.30" @@ -12898,6 +12921,18 @@ __metadata: languageName: node linkType: hard +"array.prototype.flat@npm:^1.2.5": + version: 1.3.0 + resolution: "array.prototype.flat@npm:1.3.0" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.19.2 + es-shim-unscopables: ^1.0.0 + checksum: 2a652b3e8dc0bebb6117e42a5ab5738af0203a14c27341d7bb2431467bdb4b348e2c5dc555dfcda8af0a5e4075c400b85311ded73861c87290a71a17c3e0a257 + languageName: node + linkType: hard + "array.prototype.flatmap@npm:^1.2.1, array.prototype.flatmap@npm:^1.2.5": version: 1.2.5 resolution: "array.prototype.flatmap@npm:1.2.5" @@ -13153,15 +13188,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:0.21.2": - version: 0.21.2 - resolution: "axios@npm:0.21.2" - dependencies: - follow-redirects: ^1.14.0 - checksum: 41299c21f77323e7c56ea49d2d5ed25407d24bb145a4ce3441a8db17359ae56554f9495dca6e15d343a9530e106212a684c75a4db3bedfbe19f6414a8d94378e - languageName: node - linkType: hard - "axios@npm:0.26.1": version: 0.26.1 resolution: "axios@npm:0.26.1" @@ -14398,24 +14424,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30000981, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001271": - version: 1.0.30001271 - resolution: "caniuse-lite@npm:1.0.30001271" - checksum: 0271e234e17c588fa203a34523f90c5a21ab62f41a2c3acabe96f0251593ac92f192cadd7a3dcbcf49bb28de4f57ca274d775c75700c3f38e9c42028d3becb0f - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001286": - version: 1.0.30001297 - resolution: "caniuse-lite@npm:1.0.30001297" - checksum: 4f0d298cf32c050aa1d8a7bb33ef6ff5f289e6dd6dad48b364bc2db91e03b5f0089ffa9950d6228dbd43446695cecd1990601b432a8d03c1d5b2e945cb67bade - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001317": - version: 1.0.30001319 - resolution: "caniuse-lite@npm:1.0.30001319" - checksum: 1c03cc4ca019c410d197b76604cd8605077ef124906f3debd3f026568e01a1aa3888cdfcb0d23c0786115b0b3f790486f2aa8e0cce361d3dcc5c92ff3611f73e +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30000981, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001271, caniuse-lite@npm:^1.0.30001286, caniuse-lite@npm:^1.0.30001317": + version: 1.0.30001332 + resolution: "caniuse-lite@npm:1.0.30001332" + checksum: e54182ea42ab3d2ff1440f9a6480292f7ab23c00c188df7ad65586312e4da567e8bedd5cb5fb8f0ff4193dc027a54e17e0b3c0b6db5d5a3fb61c7726ff9c45b3 languageName: node linkType: hard @@ -15105,6 +15117,13 @@ __metadata: languageName: node linkType: hard +"command-score@npm:^0.1.2": + version: 0.1.2 + resolution: "command-score@npm:0.1.2" + checksum: b733fd552d7e569070da3d474b1ed5f54785fdf3dd61670002e0a00b2eff1a547c2b6d3af3683c012f4f39c6455f9e7ee5e9997a79c08048ec37ec2195d3df08 + languageName: node + linkType: hard + "commander@npm:*, commander@npm:8.3.0, commander@npm:^8.0.0, commander@npm:^8.1.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" @@ -16916,7 +16935,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.0": +"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.0, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -18040,6 +18059,34 @@ __metadata: languageName: node linkType: hard +"es-abstract@npm:^1.19.2": + version: 1.19.5 + resolution: "es-abstract@npm:1.19.5" + dependencies: + call-bind: ^1.0.2 + es-to-primitive: ^1.2.1 + function-bind: ^1.1.1 + get-intrinsic: ^1.1.1 + get-symbol-description: ^1.0.0 + has: ^1.0.3 + has-symbols: ^1.0.3 + internal-slot: ^1.0.3 + is-callable: ^1.2.4 + is-negative-zero: ^2.0.2 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + is-string: ^1.0.7 + is-weakref: ^1.0.2 + object-inspect: ^1.12.0 + object-keys: ^1.1.1 + object.assign: ^4.1.2 + string.prototype.trimend: ^1.0.4 + string.prototype.trimstart: ^1.0.4 + unbox-primitive: ^1.0.1 + checksum: 55199b0f179a12b3b0ec9c9f2e3a27a7561686e4f88d46f9ef32c836448a336e367c14d8f792612fc83a64113896e478259e4dffbbcffb3efdd06650f6360324 + languageName: node + linkType: hard + "es-array-method-boxes-properly@npm:^1.0.0": version: 1.0.0 resolution: "es-array-method-boxes-properly@npm:1.0.0" @@ -18070,6 +18117,15 @@ __metadata: languageName: node linkType: hard +"es-shim-unscopables@npm:^1.0.0": + version: 1.0.0 + resolution: "es-shim-unscopables@npm:1.0.0" + dependencies: + has: ^1.0.3 + checksum: 83e95cadbb6ee44d3644dfad60dcad7929edbc42c85e66c3e99aefd68a3a5c5665f2686885cddb47dfeabfd77bd5ea5a7060f2092a955a729bbd8834f0d86fa1 + languageName: node + linkType: hard + "es-to-primitive@npm:^1.2.1": version: 1.2.1 resolution: "es-to-primitive@npm:1.2.1" @@ -18246,6 +18302,49 @@ __metadata: languageName: node linkType: hard +"eslint-import-resolver-node@npm:^0.3.6": + version: 0.3.6 + resolution: "eslint-import-resolver-node@npm:0.3.6" + dependencies: + debug: ^3.2.7 + resolve: ^1.20.0 + checksum: 6266733af1e112970e855a5bcc2d2058fb5ae16ad2a6d400705a86b29552b36131ffc5581b744c23d550de844206fb55e9193691619ee4dbf225c4bde526b1c8 + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.7.3": + version: 2.7.3 + resolution: "eslint-module-utils@npm:2.7.3" + dependencies: + debug: ^3.2.7 + find-up: ^2.1.0 + checksum: 77048263f309167a1e6a1e1b896bfb5ddd1d3859b2e2abbd9c32c432aee13d610d46e6820b1ca81b37fba437cf423a404bc6649be64ace9148a3062d1886a678 + languageName: node + linkType: hard + +"eslint-plugin-import@npm:^2.26.0": + version: 2.26.0 + resolution: "eslint-plugin-import@npm:2.26.0" + dependencies: + array-includes: ^3.1.4 + array.prototype.flat: ^1.2.5 + debug: ^2.6.9 + doctrine: ^2.1.0 + eslint-import-resolver-node: ^0.3.6 + eslint-module-utils: ^2.7.3 + has: ^1.0.3 + is-core-module: ^2.8.1 + is-glob: ^4.0.3 + minimatch: ^3.1.2 + object.values: ^1.1.5 + resolve: ^1.22.0 + tsconfig-paths: ^3.14.1 + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + checksum: 0bf77ad80339554481eafa2b1967449e1f816b94c7a6f9614ce33fb4083c4e6c050f10d241dd50b4975d47922880a34de1e42ea9d8e6fd663ebb768baa67e655 + languageName: node + linkType: hard + "eslint-plugin-jest@npm:26.1.2": version: 26.1.2 resolution: "eslint-plugin-jest@npm:26.1.2" @@ -19016,6 +19115,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^2.0.3": + version: 2.0.4 + resolution: "fast-equals@npm:2.0.4" + checksum: 1aac8a2e16b33e5e402bb5cd46be65f1ca331903c2c44e3bd75324e8472ee04f0acdbc6889e45fc28f9707ca3964f2a86789b32305d4d8b85dc97f61e446ef4b + languageName: node + linkType: hard + "fast-glob@npm:^2.2.6": version: 2.2.7 resolution: "fast-glob@npm:2.2.7" @@ -19324,7 +19430,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^2.0.0": +"find-up@npm:^2.0.0, find-up@npm:^2.1.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" dependencies: @@ -19395,7 +19501,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0": +"follow-redirects@npm:^1.0.0": version: 1.14.4 resolution: "follow-redirects@npm:1.14.4" peerDependenciesMeta: @@ -20424,7 +20530,7 @@ __metadata: "@grafana/e2e": "workspace:*" "@grafana/e2e-selectors": "workspace:*" "@grafana/eslint-config": 3.0.0 - "@grafana/experimental": 0.0.2-canary.22 + "@grafana/experimental": ^0.0.2-canary.25 "@grafana/google-sdk": 0.0.3 "@grafana/lezer-logql": ^0.0.11 "@grafana/runtime": "workspace:*" @@ -20434,7 +20540,7 @@ __metadata: "@grafana/tsconfig": ^1.2.0-rc1 "@grafana/ui": "workspace:*" "@jaegertracing/jaeger-ui-components": "workspace:*" - "@kusto/monaco-kusto": 5.0.0 + "@kusto/monaco-kusto": 5.1.1 "@lezer/common": 0.15.12 "@lezer/lr": 0.15.8 "@lingui/cli": 3.13.2 @@ -20472,7 +20578,7 @@ __metadata: "@testing-library/jest-dom": 5.16.2 "@testing-library/react": 12.1.4 "@testing-library/react-hooks": 7.0.2 - "@testing-library/user-event": 13.5.0 + "@testing-library/user-event": 14.1.1 "@types/angular": 1.8.4 "@types/angular-route": 1.7.2 "@types/classnames": 2.3.0 @@ -20568,6 +20674,7 @@ __metadata: enzyme-to-json: 3.6.2 eslint: 8.11.0 eslint-config-prettier: 8.5.0 + eslint-plugin-import: ^2.26.0 eslint-plugin-jest: 26.1.2 eslint-plugin-jsdoc: 38.0.6 eslint-plugin-lodash: 7.4.0 @@ -20603,6 +20710,7 @@ __metadata: js-yaml: ^4.1.0 json-source-map: 0.6.1 jsurl: ^0.1.5 + kbar: ^0.1.0-beta.30 lerna: ^4.0.0 lezer-promql: 0.22.0 lint-staged: 12.3.7 @@ -20827,6 +20935,13 @@ __metadata: languageName: node linkType: hard +"has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410 + languageName: node + linkType: hard + "has-tostringtag@npm:^1.0.0": version: 1.0.0 resolution: "has-tostringtag@npm:1.0.0" @@ -22407,6 +22522,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.2": + version: 2.0.2 + resolution: "is-negative-zero@npm:2.0.2" + checksum: f3232194c47a549da60c3d509c9a09be442507616b69454716692e37ae9f37c4dea264fb208ad0c9f3efd15a796a46b79df07c7e53c6227c32170608b809149a + languageName: node + linkType: hard + "is-npm@npm:^5.0.0": version: 5.0.0 resolution: "is-npm@npm:5.0.0" @@ -22572,6 +22694,15 @@ __metadata: languageName: node linkType: hard +"is-shared-array-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "is-shared-array-buffer@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + checksum: 9508929cf14fdc1afc9d61d723c6e8d34f5e117f0bffda4d97e7a5d88c3a8681f633a74f8e3ad1fe92d5113f9b921dc5ca44356492079612f9a247efbce7032a + languageName: node + linkType: hard + "is-ssh@npm:^1.3.0": version: 1.3.3 resolution: "is-ssh@npm:1.3.3" @@ -22665,6 +22796,15 @@ __metadata: languageName: node linkType: hard +"is-weakref@npm:^1.0.2": + version: 1.0.2 + resolution: "is-weakref@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + checksum: 95bd9a57cdcb58c63b1c401c60a474b0f45b94719c30f548c891860f051bc2231575c290a6b420c6bc6e7ed99459d424c652bd5bf9a1d5259505dc35b4bf83de + languageName: node + linkType: hard + "is-what@npm:^3.12.0": version: 3.14.1 resolution: "is-what@npm:3.14.1" @@ -24511,6 +24651,22 @@ __metadata: languageName: node linkType: hard +"kbar@npm:^0.1.0-beta.30": + version: 0.1.0-beta.33 + resolution: "kbar@npm:0.1.0-beta.33" + dependencies: + "@reach/portal": ^0.16.0 + command-score: ^0.1.2 + fast-equals: ^2.0.3 + react-virtual: ^2.8.2 + tiny-invariant: ^1.2.0 + peerDependencies: + react: ^16.0.0 || ^17.0.0 + react-dom: ^16.0.0 || ^17.0.0 + checksum: 545720900a046501157f8aab9530f62dec356438b85f728968fdc08192cc65afd903cd27e3a0c0f92a74ad6d15d808f5eb12c8b6a801cf881208e3dc2594eb8b + languageName: node + linkType: hard + "keycharm@npm:^0.2.0": version: 0.2.0 resolution: "keycharm@npm:0.2.0" @@ -26026,7 +26182,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.1.1, minimist@npm:^1.2.0, minimist@npm:^1.2.5": +"minimist@npm:^1.1.1, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6": version: 1.2.6 resolution: "minimist@npm:1.2.6" checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb @@ -31335,6 +31491,17 @@ __metadata: languageName: node linkType: hard +"react-virtual@npm:^2.8.2": + version: 2.10.4 + resolution: "react-virtual@npm:2.10.4" + dependencies: + "@reach/observe-rect": ^1.1.0 + peerDependencies: + react: ^16.6.3 || ^17.0.0 + checksum: 1bebc741b01057829a7d7f29256114caecf0597d41b187cb41e75af77f24a87c780bc1a81ec11205b78ee2e9c801fc5e36b20a9e1ab7ddc70a18dd95417795f8 + languageName: node + linkType: hard + "react-virtualized-auto-sizer@npm:1.0.6": version: 1.0.6 resolution: "react-virtualized-auto-sizer@npm:1.0.6" @@ -32156,7 +32323,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.3.2": +"resolve@npm:^1.22.0, resolve@npm:^1.3.2": version: 1.22.0 resolution: "resolve@npm:1.22.0" dependencies: @@ -32208,7 +32375,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.3.2#~builtin": +"resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.3.2#~builtin": version: 1.22.0 resolution: "resolve@patch:resolve@npm%3A1.22.0#~builtin::version=1.22.0&hash=07638b" dependencies: @@ -34937,6 +35104,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.2.0": + version: 1.2.0 + resolution: "tiny-invariant@npm:1.2.0" + checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade + languageName: node + linkType: hard + "tiny-warning@npm:^0.0.3": version: 0.0.3 resolution: "tiny-warning@npm:0.0.3" @@ -35460,6 +35634,18 @@ __metadata: languageName: node linkType: hard +"tsconfig-paths@npm:^3.14.1": + version: 3.14.1 + resolution: "tsconfig-paths@npm:3.14.1" + dependencies: + "@types/json5": ^0.0.29 + json5: ^1.0.1 + minimist: ^1.2.6 + strip-bom: ^3.0.0 + checksum: 8afa01c673ebb4782ba53d3a12df97fa837ce524f8ad38ee4e2b2fd57f5ac79abc21c574e9e9eb014d93efe7fe8214001b96233b5c6ea75bd1ea82afe17a4c6d + languageName: node + linkType: hard + "tslib@npm:2.3.1, tslib@npm:^2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1": version: 2.3.1 resolution: "tslib@npm:2.3.1"