diff --git a/.betterer.results b/.betterer.results index f5c2127333f..bfa5bfe7558 100644 --- a/.betterer.results +++ b/.betterer.results @@ -489,6 +489,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "6"], [0, 0, 0, "Unexpected any. Specify a different type.", "7"] ], + "packages/grafana-prometheus/src/test/mocks/datasource.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "packages/grafana-prometheus/src/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -958,6 +961,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`profiler\`)", "6"], [0, 0, 0, "Do not re-export imported variable (\`updateLegendValues\`)", "7"] ], + "public/app/core/navigation/mocks/routeProps.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/core/navigation/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1566,6 +1572,9 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] ], + "public/app/features/correlations/mocks/useCorrelations.mocks.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/features/correlations/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -2223,10 +2232,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] ], - "public/app/features/datasources/__mocks__/index.ts:5381": [ - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] - ], "public/app/features/datasources/components/DataSourceTypeCard.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Card components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] @@ -2597,11 +2602,6 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"] ], - "public/app/features/plugins/admin/__mocks__/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./localPlugin.mock\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./remotePlugin.mock\`)", "1"], - [0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"] - ], "public/app/features/plugins/admin/components/Badges/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./PluginDeprecatedBadge\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`./PluginDisabledBadge\`)", "1"], @@ -2622,6 +2622,16 @@ exports[`better eslint`] = { "public/app/features/plugins/admin/components/PluginDetailsPage.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/features/plugins/admin/mocks/localPlugin.mock.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/features/plugins/admin/mocks/remotePlugin.mock.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/features/plugins/admin/pages/Browse.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], @@ -2972,6 +2982,12 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Do not use any type assertions.", "3"] ], + "public/app/features/users/mocks/userMocks.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"] + ], "public/app/features/variables/adapters.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3196,6 +3212,21 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"] ], + "public/app/plugins/datasource/azuremonitor/mocks/datasource.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/azuremonitor/mocks/panelData.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] + ], + "public/app/plugins/datasource/azuremonitor/mocks/utils.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/plugins/datasource/azuremonitor/mocks/variables.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/plugins/datasource/azuremonitor/types/index.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"], @@ -3260,6 +3291,14 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], + "public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringDatasource.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] + ], + "public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringInstanceSettings.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/plugins/datasource/cloud-monitoring/types/query.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`../dataquery.gen\`)", "1"], @@ -3271,35 +3310,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/cloud-monitoring/webpack.config.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`config\`)", "0"] ], - "public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./commentOnlyQuery\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./empty\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./filterQuery\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./newCommandQuery\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "5"], - [0, 0, 0, "Do not re-export imported variable (\`./sortQuery\`)", "6"], - [0, 0, 0, "Do not re-export imported variable (\`./whitespaceQuery\`)", "7"] - ], - "public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./multiLineIncompleteQueryWithoutNamespace\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineTwoQueries\`)", "4"] - ], - "public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./afterLabelValue\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./insideLabelValue\`)", "1"] - ], - "public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./afterFunctionQuery\`)", "0"], - [0, 0, 0, "Do not re-export imported variable (\`./secondArgAfterSearchQuery\`)", "1"], - [0, 0, 0, "Do not re-export imported variable (\`./secondArgQuery\`)", "2"], - [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "3"], - [0, 0, 0, "Do not re-export imported variable (\`./thirdArgAfterSearchQuery\`)", "4"], - [0, 0, 0, "Do not re-export imported variable (\`./withinStringQuery\`)", "5"] - ], "public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], @@ -3337,6 +3347,153 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], + "public/app/plugins/datasource/cloudwatch/mocks/CloudWatchVariables.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/commentOnlyQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithFunction.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithSubquery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/singleLineFullQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/whitespaceQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/commentOnlyQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/filterQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./commentOnlyQuery\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./empty\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./filterQuery\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./newCommandQuery\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "5"], + [0, 0, 0, "Do not re-export imported variable (\`./sortQuery\`)", "6"], + [0, 0, 0, "Do not re-export imported variable (\`./whitespaceQuery\`)", "7"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/multiLineFullQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/newCommandQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/singleLineFullQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/sortQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/whitespaceQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/multilineQueries.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/newCommandQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/singleLineQueries.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"], + [0, 0, 0, "Do not use any type assertions.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Do not use any type assertions.", "5"], + [0, 0, 0, "Do not use any type assertions.", "6"], + [0, 0, 0, "Do not use any type assertions.", "7"], + [0, 0, 0, "Do not use any type assertions.", "8"], + [0, 0, 0, "Do not use any type assertions.", "9"], + [0, 0, 0, "Do not use any type assertions.", "10"], + [0, 0, 0, "Do not use any type assertions.", "11"], + [0, 0, 0, "Do not use any type assertions.", "12"], + [0, 0, 0, "Do not use any type assertions.", "13"], + [0, 0, 0, "Do not use any type assertions.", "14"], + [0, 0, 0, "Do not use any type assertions.", "15"], + [0, 0, 0, "Do not use any type assertions.", "16"], + [0, 0, 0, "Do not use any type assertions.", "17"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./multiLineFullQuery\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./multiLineIncompleteQueryWithoutNamespace\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineFullQuery\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineTwoQueries\`)", "4"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineFullQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineIncompleteQueryWithoutNamespace.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineEmptyQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineFullQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineTwoQueries.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/afterLabelValue.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./afterLabelValue\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./insideLabelValue\`)", "1"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/insideLabelValue.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/afterFunctionQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./afterFunctionQuery\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./secondArgAfterSearchQuery\`)", "1"], + [0, 0, 0, "Do not re-export imported variable (\`./secondArgQuery\`)", "2"], + [0, 0, 0, "Do not re-export imported variable (\`./singleLineEmptyQuery\`)", "3"], + [0, 0, 0, "Do not re-export imported variable (\`./thirdArgAfterSearchQuery\`)", "4"], + [0, 0, 0, "Do not re-export imported variable (\`./withinStringQuery\`)", "5"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgAfterSearchQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/singleLineEmptyQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/thirdArgAfterSearchQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/withinStringQuery.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/plugins/datasource/cloudwatch/mocks/monarch/Monaco.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], "public/app/plugins/datasource/cloudwatch/types.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3409,6 +3566,9 @@ exports[`better eslint`] = { "public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "public/app/plugins/datasource/elasticsearch/configuration/mocks/configOptions.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/plugins/datasource/elasticsearch/datasource.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3539,8 +3699,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "15"], [0, 0, 0, "Unexpected any. Specify a different type.", "16"], [0, 0, 0, "Unexpected any. Specify a different type.", "17"], - [0, 0, 0, "Unexpected any. Specify a different type.", "18"], - [0, 0, 0, "Unexpected any. Specify a different type.", "19"] + [0, 0, 0, "Unexpected any. Specify a different type.", "18"] ], "public/app/plugins/datasource/graphite/gfunc.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], @@ -3628,6 +3787,10 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"] ], + "public/app/plugins/datasource/influxdb/mocks/datasource.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], "public/app/plugins/datasource/influxdb/query_part.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -3695,6 +3858,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], + "public/app/plugins/datasource/loki/mocks/datasource.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"] + ], + "public/app/plugins/datasource/loki/mocks/metadataRequest.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5c5de6c7246..232d737f538 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -180,7 +180,6 @@ /pkg/tests/apis/query @grafana/grafana-datasources-core-services /pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend /pkg/tests/api/correlations/ @grafana/datapro -/pkg/tests/apis/secret/ @grafana/grafana-operator-experience-squad /pkg/tsdb/grafanads/ @grafana/grafana-backend-group /pkg/tsdb/opentsdb/ @grafana/partner-datasources /pkg/util/ @grafana/grafana-backend-group diff --git a/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json index bae4ac5b6b7..a757845daa9 100644 --- a/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json +++ b/devenv/dev-dashboards/panel-timeseries/timeseries-nulls.json @@ -170,8 +170,7 @@ "type": "grafana-testdata-datasource", "uid": "PD8C576611E62080A" }, - "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"name\": \"A\",\n \"fields\": [\n {\n \"name\": \"Time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Min\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Max\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Info\",\n \"type\": \"string\",\n \"typeInfo\": {\n \"frame\": \"string\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Status\",\n \"type\": \"string\",\n \"typeInfo\": {\n \"frame\": \"string\",\n \"nullable\": true\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n \"2023-06-14T08:16:41Z\",\n \"2023-06-14T08:16:50Z\",\n \"2023-06-14T08:16:55Z\",\n \"2023-06-14T08:17:15Z\",\n \"2023-06-14T08:17:46Z\",\n \"2023-06-14T08:17:59Z\",\n \"2023-06-14T08:18:20Z\",\n \"2023-06-14T08:18:25Z\"\n ],\n [\n 97.56700088585262,\n 97.51875596242603,\n 97.88367836664669,\n 97.64813860191066,\n 97.46344237059202,\n 97.06932713353162,\n 97.11185691118344,\n 97.4315327939052\n ],\n [\n 96.17381933561288,\n 96.86449303671965,\n 95.5851820937877,\n 96.94658694041154,\n 96.7433590164638,\n 96.74913605945137,\n 95.61938577725707,\n 96.24186355022567\n ],\n [\n 99.69898997339244,\n 98.68620205532211,\n 99.5800005905314,\n 98.2998754185445,\n 98.02873998081307,\n 99.01798295660049,\n 97.95260164072228,\n 98.24811606848421\n ],\n [\n \"down\",\n \"down\",\n \"up\",\n \"down\",\n \"down\",\n \"down\",\n \"up\",\n \"up\"\n ],\n [\n \"ON\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"STANDBY\",\n \"OFF\"\n ]\n ]\n }\n }\n]", - "refId": "A", + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"name\": \"A\",\n \"fields\": [\n {\n \"name\": \"Time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Min\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Max\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Status\",\n \"type\": \"string\",\n \"typeInfo\": {\n \"frame\": \"string\",\n \"nullable\": true\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1686730601000,\n 1686730610000,\n 1686730615000,\n 1686730635000,\n 1686730666000,\n 1686730679000,\n 1686730700000,\n 1686730705000\n ],\n [\n 97.56700088585262,\n 97.51875596242603,\n 97.88367836664669,\n 97.64813860191066,\n 97.46344237059202,\n 97.06932713353162,\n 97.11185691118344,\n 97.4315327939052\n ],\n [\n 96.17381933561288,\n 96.86449303671965,\n 95.5851820937877,\n 96.94658694041154,\n 96.7433590164638,\n 96.74913605945137,\n 95.61938577725707,\n 96.24186355022567\n ],\n [\n 99.69898997339244,\n 98.68620205532211,\n 99.5800005905314,\n 98.2998754185445,\n 98.02873998081307,\n 99.01798295660049,\n 97.95260164072228,\n 98.24811606848421\n ],\n [\n \"ON\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"STANDBY\",\n \"OFF\"\n ]\n ]\n }\n }\n]", "refId": "A", "scenarioId": "raw_frame" } ], @@ -292,7 +291,7 @@ "type": "grafana-testdata-datasource", "uid": "PD8C576611E62080A" }, - "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"name\": \"A\",\n \"fields\": [\n {\n \"name\": \"Time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Min\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Max\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Status\",\n \"type\": \"string\",\n \"typeInfo\": {\n \"frame\": \"string\",\n \"nullable\": true\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n \"2023-06-14T08:16:41Z\",\n \"2023-06-14T08:16:50Z\",\n \"2023-06-14T08:16:55Z\",\n \"2023-06-14T08:17:15Z\",\n \"2023-06-14T08:17:46Z\",\n \"2023-06-14T08:17:59Z\",\n \"2023-06-14T08:18:20Z\",\n \"2023-06-14T08:18:25Z\"\n ],\n [\n 97.56700088585262,\n 97.51875596242603,\n 97.88367836664669,\n 97.64813860191066,\n 97.46344237059202,\n 97.06932713353162,\n 97.11185691118344,\n 97.4315327939052\n ],\n [\n 96.17381933561288,\n 96.86449303671965,\n 95.5851820937877,\n 96.94658694041154,\n 96.7433590164638,\n 96.74913605945137,\n 95.61938577725707,\n 96.24186355022567\n ],\n [\n 99.69898997339244,\n 98.68620205532211,\n 99.5800005905314,\n 98.2998754185445,\n 98.02873998081307,\n 99.01798295660049,\n 97.95260164072228,\n 98.24811606848421\n ],\n [\n \"ON\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"STANDBY\",\n \"OFF\"\n ]\n ]\n }\n }\n]", + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"name\": \"A\",\n \"fields\": [\n {\n \"name\": \"Time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Min\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Max\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"config\": {}\n },\n {\n \"name\": \"Status\",\n \"type\": \"string\",\n \"typeInfo\": {\n \"frame\": \"string\",\n \"nullable\": true\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1686730601000,\n 1686730610000,\n 1686730615000,\n 1686730635000,\n 1686730666000,\n 1686730679000,\n 1686730700000,\n 1686730705000\n ],\n [\n 97.56700088585262,\n 97.51875596242603,\n 97.88367836664669,\n 97.64813860191066,\n 97.46344237059202,\n 97.06932713353162,\n 97.11185691118344,\n 97.4315327939052\n ],\n [\n 96.17381933561288,\n 96.86449303671965,\n 95.5851820937877,\n 96.94658694041154,\n 96.7433590164638,\n 96.74913605945137,\n 95.61938577725707,\n 96.24186355022567\n ],\n [\n 99.69898997339244,\n 98.68620205532211,\n 99.5800005905314,\n 98.2998754185445,\n 98.02873998081307,\n 99.01798295660049,\n 97.95260164072228,\n 98.24811606848421\n ],\n [\n \"ON\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"OFF\",\n \"ON\",\n \"STANDBY\",\n \"OFF\"\n ]\n ]\n }\n }\n]", "refId": "A", "scenarioId": "raw_frame" } diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index da44ded91fe..ef2ca7ab494 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -53,7 +53,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `ssoSettingsApi` | Enables the SSO settings API and the OAuth configuration UIs in Grafana | Yes | | `logsInfiniteScrolling` | Enables infinite scrolling for the Logs panel in Explore and Dashboards | Yes | | `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes | -| `lokiQueryHints` | Enables query hints for Loki | Yes | | `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | | | `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate various on-prem resources to your Grafana Cloud stack. | Yes | | `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes | @@ -103,6 +102,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches | | `pdfTables` | Enables generating table data as PDF in reporting | | `canvasPanelPanZoom` | Allow pan and zoom in canvas panel | +| `regressionTransformation` | Enables regression analysis transformation | | `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage | | `tableNextGen` | Allows access to the new react-data-grid based table component. | | `enableSCIM` | Enables SCIM support for user and group management | diff --git a/docs/sources/setup-grafana/configure-security/configure-team-sync.md b/docs/sources/setup-grafana/configure-security/configure-team-sync.md index 8d5b2ea4aed..c7c16c0f924 100644 --- a/docs/sources/setup-grafana/configure-security/configure-team-sync.md +++ b/docs/sources/setup-grafana/configure-security/configure-team-sync.md @@ -33,8 +33,8 @@ This mechanism allows Grafana to remove an existing synchronized user from a tea - [GitHub OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/github/#configure-team-synchronization) - [GitLab OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/gitlab/#configure-team-synchronization) - [Google OAuth](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/google/#configure-team-synchronization) -- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml/) +- [LDAP](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/enhanced-ldap/) +- [Okta](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/okta/#configure-team-synchronization-enterprise-only) - [SAML](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-authentication/saml/) ## Synchronize a Grafana team with an external group diff --git a/package.json b/package.json index d8e9b427281..88066d57d79 100644 --- a/package.json +++ b/package.json @@ -289,8 +289,8 @@ "@grafana/plugin-ui": "0.10.7", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.24.0", - "@grafana/scenes-react": "^6.24.0", + "@grafana/scenes": "^6.26.1", + "@grafana/scenes-react": "^6.26.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index e76e1b494ae..167962c0745 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -420,10 +420,9 @@ export interface FeatureToggles { */ tableSharedCrosshair?: boolean; /** - * Enables query hints for Loki - * @default true + * Enables regression analysis transformation */ - lokiQueryHints?: boolean; + regressionTransformation?: boolean; /** * Use the kubernetes API for feature toggle management in the frontend */ diff --git a/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.test.tsx b/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.test.tsx index 68d88b3e123..4a1536edc10 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.test.tsx +++ b/packages/grafana-prometheus/src/components/metrics-browser/MetricsBrowserContext.test.tsx @@ -7,7 +7,7 @@ import { TimeRange } from '@grafana/data'; import { DEFAULT_SERIES_LIMIT, LAST_USED_LABELS_KEY, METRIC_LABEL } from '../../constants'; import { PrometheusDatasource } from '../../datasource'; import { PrometheusLanguageProviderInterface } from '../../language_provider'; -import { getMockTimeRange } from '../../test/__mocks__/datasource'; +import { getMockTimeRange } from '../../test/mocks/datasource'; import { MetricsBrowserProvider, useMetricsBrowser } from './MetricsBrowserContext'; diff --git a/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.test.ts b/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.test.ts index 6cd2ab01869..e43b2bd0cc3 100644 --- a/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.test.ts +++ b/packages/grafana-prometheus/src/components/metrics-browser/useMetricsLabelsValues.test.ts @@ -5,7 +5,7 @@ import { TimeRange } from '@grafana/data'; import { DEFAULT_SERIES_LIMIT, EMPTY_SELECTOR, LAST_USED_LABELS_KEY, METRIC_LABEL } from '../../constants'; import { PrometheusDatasource } from '../../datasource'; import { PrometheusLanguageProvider, PrometheusLanguageProviderInterface } from '../../language_provider'; -import { getMockTimeRange } from '../../test/__mocks__/datasource'; +import { getMockTimeRange } from '../../test/mocks/datasource'; import * as selectorBuilderModule from './selectorBuilder'; import { useMetricsLabelsValues } from './useMetricsLabelsValues'; diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts index 28b55e16f73..88303e2503b 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/completions.test.ts @@ -2,7 +2,7 @@ import { config } from '@grafana/runtime'; import { SUGGESTIONS_LIMIT } from '../../../constants'; import { FUNCTIONS } from '../../../promql'; -import { getMockTimeRange } from '../../../test/__mocks__/datasource'; +import { getMockTimeRange } from '../../../test/mocks/datasource'; import { filterMetricNames, getCompletions } from './completions'; import { DataProvider, type DataProviderParams } from './data_provider'; diff --git a/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.test.tsx b/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.test.tsx index 6cbf7715552..6ff15e9f063 100644 --- a/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.test.tsx +++ b/packages/grafana-prometheus/src/configuration/AlertingSettingsOverhaul.test.tsx @@ -3,7 +3,7 @@ import { render } from '@testing-library/react'; import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; -import { createDefaultConfigOptions } from '../test/__mocks__/datasource'; +import { createDefaultConfigOptions } from '../test/mocks/datasource'; import { AlertingSettingsOverhaul } from './AlertingSettingsOverhaul'; diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx index 0b860a34d82..ab6303d5c3a 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx @@ -7,7 +7,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; import { countError } from '../constants'; -import { createDefaultConfigOptions } from '../test/__mocks__/datasource'; +import { createDefaultConfigOptions } from '../test/mocks/datasource'; import { getValueFromEventItem, PromSettings } from './PromSettings'; diff --git a/packages/grafana-prometheus/src/datasource.test.ts b/packages/grafana-prometheus/src/datasource.test.ts index a9c184781db..5b18f5e2e97 100644 --- a/packages/grafana-prometheus/src/datasource.test.ts +++ b/packages/grafana-prometheus/src/datasource.test.ts @@ -24,7 +24,7 @@ import { createDefaultPromResponse, fetchMockCalledWith, getMockTimeRange, -} from './test/__mocks__/datasource'; +} from './test/mocks/datasource'; import { PromApplication, PrometheusCacheLevel, diff --git a/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx index 8c49e0f437f..d07b1ecf402 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx @@ -8,7 +8,7 @@ import { DataSourceInstanceSettings } from '@grafana/data'; import { PrometheusDatasource } from '../../datasource'; import { PrometheusLanguageProviderInterface } from '../../language_provider'; import { EmptyLanguageProviderMock } from '../../language_provider.mock'; -import { getMockTimeRange } from '../../test/__mocks__/datasource'; +import { getMockTimeRange } from '../../test/mocks/datasource'; import { PromOptions } from '../../types'; import { MetricCombobox, MetricComboboxProps } from './MetricCombobox'; diff --git a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx index 4a1470fb82d..ae35490c097 100644 --- a/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/components/metrics-modal/MetricsModal.test.tsx @@ -7,7 +7,7 @@ import { DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data' import { PrometheusDatasource } from '../../../datasource'; import { PrometheusLanguageProviderInterface } from '../../../language_provider'; import { EmptyLanguageProviderMock } from '../../../language_provider.mock'; -import { getMockTimeRange } from '../../../test/__mocks__/datasource'; +import { getMockTimeRange } from '../../../test/mocks/datasource'; import { PromOptions } from '../../../types'; import { PromVisualQuery } from '../../types'; diff --git a/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx b/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx index 484e829e72f..b0858fb093b 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/OperationList.test.tsx @@ -7,7 +7,7 @@ import { DataSourceApi, DataSourceInstanceSettings } from '@grafana/data'; import { PrometheusDatasource } from '../../datasource'; import { PrometheusLanguageProviderInterface } from '../../language_provider'; import { EmptyLanguageProviderMock } from '../../language_provider.mock'; -import { getMockTimeRange } from '../../test/__mocks__/datasource'; +import { getMockTimeRange } from '../../test/mocks/datasource'; import { PromOptions } from '../../types'; import { addOperationInQueryBuilder } from '../testUtils'; import { PromVisualQuery } from '../types'; diff --git a/packages/grafana-prometheus/src/test/__mocks__/datasource.ts b/packages/grafana-prometheus/src/test/mocks/datasource.ts similarity index 100% rename from packages/grafana-prometheus/src/test/__mocks__/datasource.ts rename to packages/grafana-prometheus/src/test/mocks/datasource.ts diff --git a/packages/grafana-ui/src/components/uPlot/utils.ts b/packages/grafana-ui/src/components/uPlot/utils.ts index d6a14cb68ac..340ba0b4049 100644 --- a/packages/grafana-ui/src/components/uPlot/utils.ts +++ b/packages/grafana-ui/src/components/uPlot/utils.ts @@ -4,10 +4,8 @@ import { DataFrame, DisplayProcessor, DisplayValue, - ensureTimeField, Field, fieldReducers, - FieldType, getDisplayProcessor, GrafanaTheme2, reduceField, @@ -197,11 +195,7 @@ export function preparePlotData2( let vals = field.values; if (i === 0) { - if (field.type === FieldType.time) { - data[0] = ensureTimeField(field).values; - } else { - data[0] = vals; - } + data[i] = vals; return; } diff --git a/pkg/apis/secret/v0alpha1/keeper.go b/pkg/apis/secret/v0alpha1/keeper.go index 5557e6e63d5..348df2ff914 100644 --- a/pkg/apis/secret/v0alpha1/keeper.go +++ b/pkg/apis/secret/v0alpha1/keeper.go @@ -65,6 +65,22 @@ type KeeperSpec struct { HashiCorp *HashiCorpKeeperConfig `json:"hashivault,omitempty"` } +func (s *KeeperSpec) GetType() KeeperType { + if s.AWS != nil { + return AWSKeeperType + } + if s.Azure != nil { + return AzureKeeperType + } + if s.GCP != nil { + return GCPKeeperType + } + if s.HashiCorp != nil { + return HashiCorpKeeperType + } + return "" +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type KeeperList struct { metav1.TypeMeta `json:",inline"` @@ -102,9 +118,6 @@ type HashiCorpCredentials struct { Token CredentialValue `json:"token"` } -// Envelope encrytion details. -type Envelope struct{} - // Holds the way credentials are obtained. // +union type CredentialValue struct { @@ -122,6 +135,13 @@ type CredentialValue struct { ValueFromConfig string `json:"valueFromConfig,omitempty"` } +// System Keeper. +type SystemKeeperConfig struct{} + +func (s *SystemKeeperConfig) Type() KeeperType { + return "system" +} + // Remote Keepers. type AWSKeeperConfig struct { AWSCredentials `json:",inline"` diff --git a/pkg/apis/secret/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/secret/v0alpha1/zz_generated.deepcopy.go index d6441e2a51e..5dd1844463a 100644 --- a/pkg/apis/secret/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/secret/v0alpha1/zz_generated.deepcopy.go @@ -96,22 +96,6 @@ func (in *CredentialValue) DeepCopy() *CredentialValue { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Envelope) DeepCopyInto(out *Envelope) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Envelope. -func (in *Envelope) DeepCopy() *Envelope { - if in == nil { - return nil - } - out := new(Envelope) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GCPCredentials) DeepCopyInto(out *GCPCredentials) { *out = *in @@ -398,3 +382,19 @@ func (in *SecureValueStatus) DeepCopy() *SecureValueStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SystemKeeperConfig) DeepCopyInto(out *SystemKeeperConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemKeeperConfig. +func (in *SystemKeeperConfig) DeepCopy() *SystemKeeperConfig { + if in == nil { + return nil + } + out := new(SystemKeeperConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/secret/v0alpha1/zz_generated.openapi.go b/pkg/apis/secret/v0alpha1/zz_generated.openapi.go index 18b4d4a26fa..af0003fbb5c 100644 --- a/pkg/apis/secret/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/secret/v0alpha1/zz_generated.openapi.go @@ -20,7 +20,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureCredentials": schema_pkg_apis_secret_v0alpha1_AzureCredentials(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.AzureKeeperConfig": schema_pkg_apis_secret_v0alpha1_AzureKeeperConfig(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.CredentialValue": schema_pkg_apis_secret_v0alpha1_CredentialValue(ref), - "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.Envelope": schema_pkg_apis_secret_v0alpha1_Envelope(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPCredentials": schema_pkg_apis_secret_v0alpha1_GCPCredentials(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.GCPKeeperConfig": schema_pkg_apis_secret_v0alpha1_GCPKeeperConfig(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.HashiCorpCredentials": schema_pkg_apis_secret_v0alpha1_HashiCorpCredentials(ref), @@ -33,6 +32,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SecureValueList": schema_pkg_apis_secret_v0alpha1_SecureValueList(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SecureValueSpec": schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref), "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SecureValueStatus": schema_pkg_apis_secret_v0alpha1_SecureValueStatus(ref), + "github.com/grafana/grafana/pkg/apis/secret/v0alpha1.SystemKeeperConfig": schema_pkg_apis_secret_v0alpha1_SystemKeeperConfig(ref), } } @@ -235,17 +235,6 @@ func schema_pkg_apis_secret_v0alpha1_CredentialValue(ref common.ReferenceCallbac } } -func schema_pkg_apis_secret_v0alpha1_Envelope(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Envelope encrytion details.", - Type: []string{"object"}, - }, - }, - } -} - func schema_pkg_apis_secret_v0alpha1_GCPCredentials(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -771,3 +760,14 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueStatus(ref common.ReferenceCallb }, } } + +func schema_pkg_apis_secret_v0alpha1_SystemKeeperConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "System Keeper.", + Type: []string{"object"}, + }, + }, + } +} diff --git a/pkg/registry/apis/apis.go b/pkg/registry/apis/apis.go index e100c3c1af7..4ae591b24da 100644 --- a/pkg/registry/apis/apis.go +++ b/pkg/registry/apis/apis.go @@ -27,9 +27,9 @@ func ProvideRegistryServiceSink( _ *iam.IdentityAccessManagementAPIBuilder, _ *query.QueryAPIBuilder, _ *userstorage.UserStorageAPIBuilder, - _ *secret.SecretAPIBuilder, _ *provisioning.APIBuilder, _ *ofrep.APIBuilder, + _ *secret.DependencyRegisterer, ) *Service { return &Service{} } diff --git a/pkg/registry/apis/secret/accesscontrol.go b/pkg/registry/apis/secret/accesscontrol.go index e93fe22d1cc..1b4c050fb58 100644 --- a/pkg/registry/apis/secret/accesscontrol.go +++ b/pkg/registry/apis/secret/accesscontrol.go @@ -27,12 +27,12 @@ var ( ScopeAllKeepers = ScopeProviderSecretKeepers.GetResourceAllScope() ) -func RegisterAccessControlRoles(service accesscontrol.Service) error { +func registerAccessControlRoles(service accesscontrol.Service) error { // SecureValues secureValuesReader := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.securevalues:reader", - DisplayName: "Secrets Manager secure values reader", + DisplayName: "Secure Values Reader", Description: "Read and list secure values.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ @@ -48,7 +48,7 @@ func RegisterAccessControlRoles(service accesscontrol.Service) error { secureValuesWriter := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.securevalues:writer", - DisplayName: "Secrets Manager secure values writer", + DisplayName: "Secure Values Writer", Description: "Create, update and delete secure values.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ @@ -77,7 +77,7 @@ func RegisterAccessControlRoles(service accesscontrol.Service) error { keepersReader := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.keepers:reader", - DisplayName: "Secrets Manager keepers reader", + DisplayName: "Keepers Reader", Description: "Read and list keepers.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ @@ -93,7 +93,7 @@ func RegisterAccessControlRoles(service accesscontrol.Service) error { keepersWriter := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.keepers:writer", - DisplayName: "Secrets Manager keepers writer", + DisplayName: "Keepers Writer", Description: "Create, update and delete keepers.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ diff --git a/pkg/registry/apis/secret/decrypt/authorizer.go b/pkg/registry/apis/secret/decrypt/authorizer.go index de76ca759f6..6350e5f80af 100644 --- a/pkg/registry/apis/secret/decrypt/authorizer.go +++ b/pkg/registry/apis/secret/decrypt/authorizer.go @@ -6,6 +6,8 @@ import ( "github.com/grafana/authlib/authn" claims "github.com/grafana/authlib/types" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" @@ -13,17 +15,32 @@ import ( // decryptAuthorizer is the authorizer implementation for decrypt operations. type decryptAuthorizer struct { + tracer trace.Tracer allowList contracts.DecryptAllowList } -func ProvideDecryptAuthorizer(allowList contracts.DecryptAllowList) contracts.DecryptAuthorizer { +func ProvideDecryptAuthorizer(tracer trace.Tracer, allowList contracts.DecryptAllowList) contracts.DecryptAuthorizer { return &decryptAuthorizer{ + tracer: tracer, allowList: allowList, } } // authorize checks whether the auth info token has the right permissions to decrypt the secure value. -func (a *decryptAuthorizer) Authorize(ctx context.Context, secureValueName string, secureValueDecrypters []string) (string, bool) { +func (a *decryptAuthorizer) Authorize(ctx context.Context, secureValueName string, secureValueDecrypters []string) (id string, isAllowed bool) { + ctx, span := a.tracer.Start(ctx, "DecryptAuthorizer.Authorize", trace.WithAttributes( + attribute.String("name", secureValueName), + attribute.StringSlice("decrypters", secureValueDecrypters), + )) + defer span.End() + + defer func() { + if id != "" { + span.SetAttributes(attribute.String("serviceIdentity", id)) + } + span.SetAttributes(attribute.Bool("allowed", isAllowed)) + }() + authInfo, ok := claims.AuthInfoFrom(ctx) if !ok { return "", false diff --git a/pkg/registry/apis/secret/decrypt/authorizer_test.go b/pkg/registry/apis/secret/decrypt/authorizer_test.go index bd808a1740c..3e4cd59ef31 100644 --- a/pkg/registry/apis/secret/decrypt/authorizer_test.go +++ b/pkg/registry/apis/secret/decrypt/authorizer_test.go @@ -7,14 +7,17 @@ import ( "github.com/grafana/authlib/authn" "github.com/grafana/authlib/types" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" "github.com/grafana/grafana/pkg/apimachinery/identity" ) func TestDecryptAuthorizer(t *testing.T) { + tracer := noop.NewTracerProvider().Tracer("test") + t.Run("when no auth info is present, it returns false", func(t *testing.T) { ctx := context.Background() - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) identity, allowed := authorizer.Authorize(ctx, "", nil) require.Empty(t, identity) @@ -23,7 +26,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when token permissions are empty, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{}) - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) identity, allowed := authorizer.Authorize(ctx, "", nil) require.NotEmpty(t, identity) @@ -32,7 +35,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when service identity is empty, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "", []string{}) - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) identity, allowed := authorizer.Authorize(ctx, "", nil) require.Empty(t, identity) @@ -40,7 +43,7 @@ func TestDecryptAuthorizer(t *testing.T) { }) t.Run("when permission format is malformed (missing verb), it returns false", func(t *testing.T) { - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) // nameless ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues"}) @@ -56,7 +59,7 @@ func TestDecryptAuthorizer(t *testing.T) { }) t.Run("when permission verb is not exactly `decrypt`, it returns false", func(t *testing.T) { - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) // nameless ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:*"}) @@ -73,7 +76,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when permission does not have 2 or 3 parts, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app:decrypt"}) - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) identity, allowed := authorizer.Authorize(ctx, "", nil) require.NotEmpty(t, identity) @@ -82,7 +85,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when permission has group that is not `secret.grafana.app`, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"wrong.group/securevalues/invalid:decrypt"}) - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) identity, allowed := authorizer.Authorize(ctx, "", nil) require.NotEmpty(t, identity) @@ -90,7 +93,7 @@ func TestDecryptAuthorizer(t *testing.T) { }) t.Run("when permission has resource that is not `securevalues`, it returns false", func(t *testing.T) { - authorizer := ProvideDecryptAuthorizer(nil) + authorizer := ProvideDecryptAuthorizer(tracer, nil) // nameless ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/invalid-resource:decrypt"}) @@ -107,7 +110,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when the identity is not in the allow list, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"allowed1": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"allowed1": {}}) identity, allowed := authorizer.Authorize(ctx, "", nil) require.NotEmpty(t, identity) @@ -115,7 +118,7 @@ func TestDecryptAuthorizer(t *testing.T) { }) t.Run("when the identity doesn't match any allowed decrypters, it returns false", func(t *testing.T) { - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) // nameless ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) @@ -131,7 +134,7 @@ func TestDecryptAuthorizer(t *testing.T) { }) t.Run("when the identity matches an allowed decrypter, it returns true", func(t *testing.T) { - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) // nameless ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) @@ -154,7 +157,7 @@ func TestDecryptAuthorizer(t *testing.T) { "wrong.group/securevalues/group2:decrypt", "secret.grafana.app/securevalues/identity:decrypt", // old style of identity+permission }) - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) identity, allowed := authorizer.Authorize(ctx, "name1", []string{"identity"}) require.True(t, allowed) @@ -167,7 +170,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when empty secure value name with specific permission, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/name:decrypt"}) - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) require.Equal(t, "identity", identity) @@ -176,7 +179,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when permission has an extra / but no name, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues/:decrypt"}) - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) require.Equal(t, "identity", identity) @@ -185,7 +188,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when the decrypters list is empty, meaning nothing can decrypt the secure value, it returns false", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity", []string{"secret.grafana.app/securevalues:decrypt"}) - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) identity, allowed := authorizer.Authorize(ctx, "name", []string{}) require.Equal(t, "identity", identity) @@ -194,7 +197,7 @@ func TestDecryptAuthorizer(t *testing.T) { t.Run("when one of decrypters matches the identity, it returns true", func(t *testing.T) { ctx := createAuthContext(context.Background(), "identity1", []string{"secret.grafana.app/securevalues:decrypt"}) - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity1": {}, "identity2": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity1": {}, "identity2": {}}) identity, allowed := authorizer.Authorize(ctx, "", []string{"identity1", "identity2", "identity3"}) require.Equal(t, "identity1", identity) @@ -202,7 +205,7 @@ func TestDecryptAuthorizer(t *testing.T) { }) t.Run("permissions must be case-sensitive and return false", func(t *testing.T) { - authorizer := ProvideDecryptAuthorizer(map[string]struct{}{"identity": {}}) + authorizer := ProvideDecryptAuthorizer(tracer, map[string]struct{}{"identity": {}}) ctx := createAuthContext(context.Background(), "identity", []string{"SECRET.grafana.app/securevalues:decrypt"}) identity, allowed := authorizer.Authorize(ctx, "", []string{"identity"}) diff --git a/pkg/registry/apis/secret/encryption/cipher/service/service.go b/pkg/registry/apis/secret/encryption/cipher/service/service.go index 4c3c0103769..9bdf0cc714f 100644 --- a/pkg/registry/apis/secret/encryption/cipher/service/service.go +++ b/pkg/registry/apis/secret/encryption/cipher/service/service.go @@ -7,9 +7,9 @@ import ( "fmt" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher" @@ -24,7 +24,7 @@ const ( // Service must not be used for cipher. // Use secrets.Service implementing envelope encryption instead. type Service struct { - tracer tracing.Tracer + tracer trace.Tracer log log.Logger cfg *setting.Cfg @@ -35,7 +35,7 @@ type Service struct { } func NewEncryptionService( - tracer tracing.Tracer, + tracer trace.Tracer, usageMetrics usagestats.Service, cfg *setting.Cfg, ) (*Service, error) { @@ -101,7 +101,7 @@ func (s *Service) registerUsageMetrics() { } func (s *Service) Decrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) { - ctx, span := s.tracer.Start(ctx, "cipher.service.Decrypt") + ctx, span := s.tracer.Start(ctx, "CipherService.Decrypt") defer span.End() var err error @@ -163,7 +163,7 @@ func (s *Service) deriveEncryptionAlgorithm(payload []byte) (string, []byte, err } func (s *Service) Encrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) { - ctx, span := s.tracer.Start(ctx, "cipher.service.Encrypt") + ctx, span := s.tracer.Start(ctx, "CipherService.Encrypt") defer span.End() var err error diff --git a/pkg/registry/apis/secret/encryption/cipher/service/service_test.go b/pkg/registry/apis/secret/encryption/cipher/service/service_test.go index 462b1011508..7a207797ce8 100644 --- a/pkg/registry/apis/secret/encryption/cipher/service/service_test.go +++ b/pkg/registry/apis/secret/encryption/cipher/service/service_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher" "github.com/grafana/grafana/pkg/setting" @@ -29,7 +29,7 @@ func newGcmService(t *testing.T) *Service { }, } - svc, err := NewEncryptionService(tracing.InitializeTracerForTest(), usageStats, settings) + svc, err := NewEncryptionService(noop.NewTracerProvider().Tracer("test"), usageStats, settings) require.NoError(t, err, "failed to set up encryption service") return svc } diff --git a/pkg/registry/apis/secret/register.go b/pkg/registry/apis/secret/register.go index d0bb292e695..f43b164f6b4 100644 --- a/pkg/registry/apis/secret/register.go +++ b/pkg/registry/apis/secret/register.go @@ -4,268 +4,40 @@ import ( "context" "fmt" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/kube-openapi/pkg/common" - - claims "github.com/grafana/authlib/types" - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/reststorage" "github.com/grafana/grafana/pkg/services/accesscontrol" - authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" - "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) -var ( - _ builder.APIGroupBuilder = (*SecretAPIBuilder)(nil) - _ builder.APIGroupMutation = (*SecretAPIBuilder)(nil) - _ builder.APIGroupValidation = (*SecretAPIBuilder)(nil) -) +// DependencyRegisterer is set to satisfy wire gen and make sure the `RegisterDependencies` is called. +type DependencyRegisterer struct{} -type SecretAPIBuilder struct { - tracer tracing.Tracer - secureValueMetadataStorage contracts.SecureValueMetadataStorage - keeperMetadataStorage contracts.KeeperMetadataStorage - accessClient claims.AccessClient - decryptersAllowList map[string]struct{} -} - -func NewSecretAPIBuilder( - tracer tracing.Tracer, - secureValueMetadataStorage contracts.SecureValueMetadataStorage, - keeperMetadataStorage contracts.KeeperMetadataStorage, - accessClient claims.AccessClient, - decryptersAllowList map[string]struct{}, -) *SecretAPIBuilder { - return &SecretAPIBuilder{tracer, secureValueMetadataStorage, keeperMetadataStorage, accessClient, decryptersAllowList} -} - -func RegisterAPIService( +func RegisterDependencies( features featuremgmt.FeatureToggles, cfg *setting.Cfg, - apiregistration builder.APIRegistrar, - tracer tracing.Tracer, - secureValueMetadataStorage contracts.SecureValueMetadataStorage, - keeperMetadataStorage contracts.KeeperMetadataStorage, - accessClient claims.AccessClient, - accessControlService accesscontrol.Service, secretDBMigrator contracts.SecretDBMigrator, -) (*SecretAPIBuilder, error) { - // Skip registration unless opting into experimental apis and the secrets management app platform flag. - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || - !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { + accessControlService accesscontrol.Service, +) (*DependencyRegisterer, error) { + if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { return nil, nil } - // Some DBs that claim to be MySQL/Postgres-compatible might not support table locking. - lockDatabase := cfg.Raw.Section("database").Key("migration_locking").MustBool(true) - - if err := secretDBMigrator.RunMigrations(context.Background(), lockDatabase); err != nil { - return nil, fmt.Errorf("running secret database migrations: %w", err) + // Permissions for requests in multi-tenant mode will come from HG. + if err := registerAccessControlRoles(accessControlService); err != nil { + return nil, fmt.Errorf("registering access control roles: %w", err) } - if err := RegisterAccessControlRoles(accessControlService); err != nil { - return nil, fmt.Errorf("register secret access control roles: %w", err) - } + // We shouldn't need to create the DB in HG, as that will use the MT api server. + if cfg.StackID == "" { + // Some DBs that claim to be MySQL/Postgres-compatible might not support table locking. + lockDatabase := cfg.Raw.Section("database").Key("migration_locking").MustBool(true) - builder := NewSecretAPIBuilder( - tracer, - secureValueMetadataStorage, - keeperMetadataStorage, - accessClient, - nil, // OSS does not need an allow list. - ) - - apiregistration.RegisterAPI(builder) - - return builder, nil -} - -// GetGroupVersion returns the tuple of `group` and `version` for the API which uniquely identifies it. -func (b *SecretAPIBuilder) GetGroupVersion() schema.GroupVersion { - return secretv0alpha1.SchemeGroupVersion -} - -// InstallSchema is called by the `apiserver` which exposes the defined kinds. -func (b *SecretAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - err := secretv0alpha1.AddKnownTypes(scheme, secretv0alpha1.VERSION) - if err != nil { - return err - } - - // Link this version to the internal representation. - // This is used for server-side-apply (PATCH), and avoids the error: - // "no kind is registered for the type" - err = secretv0alpha1.AddKnownTypes(scheme, runtime.APIVersionInternal) - if err != nil { - return err - } - - // Internal Kubernetes metadata API. Presumably to display the available APIs? - // e.g. http://localhost:3000/apis/secret.grafana.app/v0alpha1 - metav1.AddToGroupVersion(scheme, secretv0alpha1.SchemeGroupVersion) - - // This sets the priority in case we have multiple versions. - // By default Kubernetes will only let you use `kubectl get ` with one version. - // In case there are multiple versions, we'd need to pass the full path with the `--raw` flag. - if err := scheme.SetVersionPriority(secretv0alpha1.SchemeGroupVersion); err != nil { - return fmt.Errorf("scheme set version priority: %w", err) - } - - return nil -} - -func (b *SecretAPIBuilder) AllowedV0Alpha1Resources() []string { - return nil -} - -// UpdateAPIGroupInfo is called when creating a generic API server for this group of kinds. -func (b *SecretAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { - secureValueResource := secretv0alpha1.SecureValuesResourceInfo - keeperResource := secretv0alpha1.KeeperResourceInfo - - // rest.Storage is a generic interface for RESTful storage services. - // The constructors need to at least implement this interface, but will most likely implement - // other interfaces that equal to different operations like `get`, `list` and so on. - secureRestStorage := map[string]rest.Storage{ - // Default path for `securevalue`. - // The `reststorage.SecureValueRest` struct will implement interfaces for CRUDL operations on `securevalue`. - secureValueResource.StoragePath(): reststorage.NewSecureValueRest(b.secureValueMetadataStorage, secureValueResource), - - // The `reststorage.KeeperRest` struct will implement interfaces for CRUDL operations on `keeper`. - keeperResource.StoragePath(): reststorage.NewKeeperRest(b.keeperMetadataStorage, b.accessClient, keeperResource), - } - - apiGroupInfo.VersionedResourcesStorageMap[secretv0alpha1.VERSION] = secureRestStorage - return nil -} - -// GetOpenAPIDefinitions, is this only for documentation? -func (b *SecretAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return secretv0alpha1.GetOpenAPIDefinitions -} - -// GetAuthorizer decides whether the request is allowed, denied or no opinion based on credentials and request attributes. -// Usually most resource are stored in folders (e.g. alerts, dashboards), which allows users to manage permissions at folder level, -// rather than at resource level which also has the benefit of lowering the load on AuthZ side, since instead of storing access to -// a single dashboard, you'd store access to all dashboards in a specific folder. -// For Secrets, this is not the case, but if we want to make it so, we need to update this ResourceAuthorizer to check the containing folder. -// If we ever want to do that, get guidance from IAM first as well. -func (b *SecretAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return authsvc.NewResourceAuthorizer(b.accessClient) -} - -// Validate is called in `Create`, `Update` and `Delete` REST funcs, if the body calls the argument `rest.ValidateObjectFunc`. -func (b *SecretAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - obj := a.GetObject() - operation := a.GetOperation() - - if obj == nil || operation == admission.Connect { - return nil // This is normal for sub-resource - } - - groupKind := obj.GetObjectKind().GroupVersionKind().GroupKind() - - // Generic validations for all kinds. At this point the name+namespace must not be empty. - if a.GetName() == "" { - return apierrors.NewInvalid( - groupKind, - a.GetName(), - field.ErrorList{field.Required(field.NewPath("metadata", "name"), "a `name` is required")}, - ) - } - - if a.GetNamespace() == "" { - return apierrors.NewInvalid( - groupKind, - a.GetName(), - field.ErrorList{field.Required(field.NewPath("metadata", "namespace"), "a `namespace` is required")}, - ) - } - - switch typedObj := obj.(type) { - case *secretv0alpha1.SecureValue: - var oldObj *secretv0alpha1.SecureValue - - if a.GetOldObject() != nil { - var ok bool - - oldObj, ok = a.GetOldObject().(*secretv0alpha1.SecureValue) - if !ok { - return apierrors.NewBadRequest(fmt.Sprintf("old object is not a SecureValue, found %T", a.GetOldObject())) - } - } - - if errs := reststorage.ValidateSecureValue(typedObj, oldObj, operation, b.decryptersAllowList); len(errs) > 0 { - return apierrors.NewInvalid(groupKind, a.GetName(), errs) - } - - return nil - case *secretv0alpha1.Keeper: - if errs := reststorage.ValidateKeeper(typedObj, operation); len(errs) > 0 { - return apierrors.NewInvalid(groupKind, a.GetName(), errs) - } - - return nil - } - - return apierrors.NewBadRequest(fmt.Sprintf("unknown spec %T", obj)) -} - -func (b *SecretAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - obj := a.GetObject() - operation := a.GetOperation() - - if obj == nil || operation == admission.Connect { - return nil // This is normal for sub-resource - } - - // When creating a resource and the name is empty, we need to generate one. - if operation == admission.Create && a.GetName() == "" { - generatedName, err := util.GetRandomString(8) - if err != nil { - return fmt.Errorf("generate random string: %w", err) - } - - switch typedObj := obj.(type) { - case *secretv0alpha1.SecureValue: - optionalPrefix := typedObj.GenerateName - if optionalPrefix == "" { - optionalPrefix = "sv-" - } - - typedObj.Name = optionalPrefix + generatedName - - case *secretv0alpha1.Keeper: - optionalPrefix := typedObj.GenerateName - if optionalPrefix == "" { - optionalPrefix = "kp-" - } - - typedObj.Name = optionalPrefix + generatedName + // This is needed to wire up and run DB migrations for Secrets Manager, which is not run by the generic OSS DB migrator. + if err := secretDBMigrator.RunMigrations(context.Background(), lockDatabase); err != nil { + return nil, fmt.Errorf("running secret database migrations: %w", err) } } - // On any mutation to a `SecureValue`, override the `phase` as `Pending` and an empty `message`. - if operation == admission.Create || operation == admission.Update { - sv, ok := obj.(*secretv0alpha1.SecureValue) - if ok && sv != nil { - sv.Status.Phase = secretv0alpha1.SecureValuePhasePending - sv.Status.Message = "" - } - } - - return nil + return &DependencyRegisterer{}, nil } diff --git a/pkg/registry/apis/secret/reststorage/keeper_rest.go b/pkg/registry/apis/secret/reststorage/keeper_rest.go deleted file mode 100644 index c7c2eb25e8f..00000000000 --- a/pkg/registry/apis/secret/reststorage/keeper_rest.go +++ /dev/null @@ -1,386 +0,0 @@ -package reststorage - -import ( - "context" - "errors" - "fmt" - "strings" - - claims "github.com/grafana/authlib/types" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" -) - -var ( - _ rest.Scoper = (*KeeperRest)(nil) - _ rest.SingularNameProvider = (*KeeperRest)(nil) - _ rest.Getter = (*KeeperRest)(nil) - _ rest.Lister = (*KeeperRest)(nil) - _ rest.Storage = (*KeeperRest)(nil) - _ rest.Creater = (*KeeperRest)(nil) - _ rest.Updater = (*KeeperRest)(nil) - _ rest.GracefulDeleter = (*KeeperRest)(nil) -) - -// KeeperRest is an implementation of CRUDL operations on a `keeper` backed by TODO. -type KeeperRest struct { - storage contracts.KeeperMetadataStorage - accessClient claims.AccessClient - resource utils.ResourceInfo - tableConverter rest.TableConvertor -} - -// NewKeeperRest is a returns a constructed `*KeeperRest`. -func NewKeeperRest(storage contracts.KeeperMetadataStorage, accessClient claims.AccessClient, resource utils.ResourceInfo) *KeeperRest { - return &KeeperRest{storage, accessClient, resource, resource.TableConverter()} -} - -// New returns an empty `*Keeper` that is used by the `Create` method. -func (s *KeeperRest) New() runtime.Object { - return s.resource.NewFunc() -} - -// Destroy is called when? [TODO] -func (s *KeeperRest) Destroy() {} - -// NamespaceScoped returns `true` because the storage is namespaced (== org). -func (s *KeeperRest) NamespaceScoped() bool { - return true -} - -// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. -func (s *KeeperRest) GetSingularName() string { - return s.resource.GetSingularName() -} - -// NewList returns an empty `*KeeperList` that is used by the `List` method. -func (s *KeeperRest) NewList() runtime.Object { - return s.resource.NewListFunc() -} - -// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. -func (s *KeeperRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -// List calls the inner `store` (persistence) and returns a list of `Keepers` within a `namespace` filtered by the `options`. -func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing auth info in context") - } - - hasPermissionFor, err := s.accessClient.Compile(ctx, user, claims.ListRequest{ - Group: secretv0alpha1.GROUP, - Resource: secretv0alpha1.KeeperResourceInfo.GetName(), - Namespace: namespace, - Verb: utils.VerbGet, - }) - if err != nil { - return nil, fmt.Errorf("failed to compile checker: %w", err) - } - - labelSelector := options.LabelSelector - if labelSelector == nil { - labelSelector = labels.Everything() - } - - keepersList, err := s.storage.List(ctx, xkube.Namespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list keepers: %w", err) - } - - allowedKeepers := make([]secretv0alpha1.Keeper, 0) - - for _, keeper := range keepersList { - // Check whether the user has permission to access this specific Keeper in the namespace. - if !hasPermissionFor(keeper.Name, "") { - continue - } - - if labelSelector.Matches(labels.Set(keeper.Labels)) { - allowedKeepers = append(allowedKeepers, keeper) - } - } - - return &secretv0alpha1.KeeperList{ - Items: allowedKeepers, - }, nil -} - -// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`. -func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - // TODO: readopts - kp, err := s.storage.Read(ctx, xkube.Namespace(namespace), name, contracts.ReadOpts{}) - if err != nil { - if errors.Is(err, contracts.ErrKeeperNotFound) { - return nil, s.resource.NewNotFound(name) - } - return nil, fmt.Errorf("failed to read keeper: %w", err) - } - - return kp, nil -} - -// Create a new `Keeper`. Does some validation and allows empty `name` (generated). -func (s *KeeperRest) Create( - ctx context.Context, - obj runtime.Object, - createValidation rest.ValidateObjectFunc, - options *metav1.CreateOptions, -) (runtime.Object, error) { - kp, ok := obj.(*secretv0alpha1.Keeper) - if !ok { - return nil, fmt.Errorf("expected Keeper for create") - } - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing auth info in context") - } - - if err := createValidation(ctx, obj); err != nil { - return nil, err - } - - createdKeeper, err := s.storage.Create(ctx, kp, user.GetUID()) - if err != nil { - var kErr xkube.ErrorLister - if errors.As(err, &kErr) { - return nil, apierrors.NewInvalid(kp.GroupVersionKind().GroupKind(), kp.Name, kErr.ErrorList()) - } - - return nil, fmt.Errorf("failed to create keeper: %w", err) - } - - return createdKeeper, nil -} - -// Update a `Keeper`'s `value`. The second return parameter indicates whether the resource was newly created. -func (s *KeeperRest) Update( - ctx context.Context, - name string, - objInfo rest.UpdatedObjectInfo, - createValidation rest.ValidateObjectFunc, - updateValidation rest.ValidateObjectUpdateFunc, - forceAllowCreate bool, - options *metav1.UpdateOptions, -) (runtime.Object, bool, error) { - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing auth info in context") - } - - oldObj, err := s.Get(ctx, name, &metav1.GetOptions{}) - if err != nil { - return nil, false, err - } - - // Makes sure the UID and ResourceVersion are OK. - // TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually. - newObj, err := objInfo.UpdatedObject(ctx, oldObj) - if err != nil { - return nil, false, fmt.Errorf("k8s updated object: %w", err) - } - - // The current supported behavior for `Update` is to replace the entire `spec` with the new one. - // Each provider-specific setting of a keeper lives at the top-level, so it makes it possible to change a provider - // during an update. Otherwise both old and new providers would be merged in the `newObj` which is not allowed. - if err := updateValidation(ctx, newObj, oldObj); err != nil { - return nil, false, err - } - - newKeeper, ok := newObj.(*secretv0alpha1.Keeper) - if !ok { - return nil, false, fmt.Errorf("expected Keeper for update") - } - - // TODO: do we need to do this here again? Probably not, but double-check! - newKeeper.Annotations = xkube.CleanAnnotations(newKeeper.Annotations) - - // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? - updatedKeeper, err := s.storage.Update(ctx, newKeeper, user.GetUID()) - if err != nil { - var kErr xkube.ErrorLister - if errors.As(err, &kErr) { - return nil, false, apierrors.NewInvalid(newKeeper.GroupVersionKind().GroupKind(), newKeeper.Name, kErr.ErrorList()) - } - - return nil, false, fmt.Errorf("failed to update keeper: %w", err) - } - - return updatedKeeper, false, nil -} - -// Delete calls the inner `store` (persistence) in order to delete the `Keeper`. -// The second return parameter `bool` indicates whether the delete was intant or not. It always is for `Keepers`. -func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing namespace") - } - - err := s.storage.Delete(ctx, xkube.Namespace(namespace), name) - if err != nil { - if errors.Is(err, contracts.ErrKeeperNotFound) { - return nil, false, s.resource.NewNotFound(name) - } - return nil, false, fmt.Errorf("failed to delete keeper: %w", err) - } - - return nil, true, nil -} - -// ValidateKeeper does basic spec validation of a keeper. -func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation) field.ErrorList { - // Only validate Create and Update for now. - if operation != admission.Create && operation != admission.Update { - return nil - } - - errs := make(field.ErrorList, 0) - - if keeper.Spec.Description == "" { - errs = append(errs, field.Required(field.NewPath("spec", "description"), "a `description` is required")) - } - - // Only one keeper type can be configured. Return early and don't validate the specific keeper fields. - if err := validateKeepers(keeper); err != nil { - errs = append(errs, err) - - return errs - } - - if keeper.Spec.AWS != nil { - if err := validateCredentialValue(field.NewPath("spec", "aws", "accessKeyId"), keeper.Spec.AWS.AccessKeyID); err != nil { - errs = append(errs, err) - } - - if err := validateCredentialValue(field.NewPath("spec", "aws", "secretAccessKey"), keeper.Spec.AWS.SecretAccessKey); err != nil { - errs = append(errs, err) - } - } - - if keeper.Spec.Azure != nil { - if keeper.Spec.Azure.KeyVaultName == "" { - errs = append(errs, field.Required(field.NewPath("spec", "azure", "keyVaultName"), "a `keyVaultName` is required")) - } - - if keeper.Spec.Azure.TenantID == "" { - errs = append(errs, field.Required(field.NewPath("spec", "azure", "tenantId"), "a `tenantId` is required")) - } - - if keeper.Spec.Azure.ClientID == "" { - errs = append(errs, field.Required(field.NewPath("spec", "azure", "clientId"), "a `clientId` is required")) - } - - if err := validateCredentialValue(field.NewPath("spec", "azure", "clientSecret"), keeper.Spec.Azure.ClientSecret); err != nil { - errs = append(errs, err) - } - } - - if keeper.Spec.GCP != nil { - if keeper.Spec.GCP.ProjectID == "" { - errs = append(errs, field.Required(field.NewPath("spec", "gcp", "projectId"), "a `projectId` is required")) - } - - if keeper.Spec.GCP.CredentialsFile == "" { - errs = append(errs, field.Required(field.NewPath("spec", "gcp", "credentialsFile"), "a `credentialsFile` is required")) - } - } - - if keeper.Spec.HashiCorp != nil { - if keeper.Spec.HashiCorp.Address == "" { - errs = append(errs, field.Required(field.NewPath("spec", "hashicorp", "address"), "a `address` is required")) - } - - if err := validateCredentialValue(field.NewPath("spec", "hashicorp", "token"), keeper.Spec.HashiCorp.Token); err != nil { - errs = append(errs, err) - } - } - - return errs -} - -func validateKeepers(keeper *secretv0alpha1.Keeper) *field.Error { - availableKeepers := map[string]bool{ - "aws": keeper.Spec.AWS != nil, - "azure": keeper.Spec.Azure != nil, - "gcp": keeper.Spec.GCP != nil, - "hashicorp": keeper.Spec.HashiCorp != nil, - } - - configuredKeepers := make([]string, 0) - - for keeperKind, notNil := range availableKeepers { - if notNil { - configuredKeepers = append(configuredKeepers, keeperKind) - } - } - - if len(configuredKeepers) == 0 { - return field.Required(field.NewPath("spec"), "at least one `keeper` must be present") - } - - if len(configuredKeepers) > 1 { - return field.Invalid( - field.NewPath("spec"), - strings.Join(configuredKeepers, " & "), - "only one `keeper` can be present at a time but found more", - ) - } - - return nil -} - -func validateCredentialValue(path *field.Path, credentials secretv0alpha1.CredentialValue) *field.Error { - availableOptions := map[string]bool{ - "secureValueName": credentials.SecureValueName != "", - "valueFromEnv": credentials.ValueFromEnv != "", - "valueFromConfig": credentials.ValueFromConfig != "", - } - - configuredCredentials := make([]string, 0) - - for credentialKind, notEmpty := range availableOptions { - if notEmpty { - configuredCredentials = append(configuredCredentials, credentialKind) - } - } - - if len(configuredCredentials) == 0 { - return field.Required(path, "one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present") - } - - if len(configuredCredentials) > 1 { - return field.Invalid( - path, - strings.Join(configuredCredentials, " & "), - "only one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present at a time but found more", - ) - } - - return nil -} diff --git a/pkg/registry/apis/secret/reststorage/keeper_rest_test.go b/pkg/registry/apis/secret/reststorage/keeper_rest_test.go deleted file mode 100644 index 0191b79458d..00000000000 --- a/pkg/registry/apis/secret/reststorage/keeper_rest_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package reststorage - -import ( - "testing" - - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/stretchr/testify/require" - "k8s.io/apiserver/pkg/admission" -) - -func TestValidateKeeper(t *testing.T) { - t.Run("when creating a new keeper", func(t *testing.T) { - t.Run("the `description` must be present", func(t *testing.T) { - keeper := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - AWS: &secretv0alpha1.AWSKeeperConfig{ - AWSCredentials: secretv0alpha1.AWSCredentials{ - AccessKeyID: secretv0alpha1.CredentialValue{ValueFromEnv: "some-value"}, - SecretAccessKey: secretv0alpha1.CredentialValue{ValueFromEnv: "some-value"}, - KMSKeyID: "kms-key-id", - }, - }, - }, - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.description", errs[0].Field) - }) - }) - - t.Run("only one `keeper` must be present", func(t *testing.T) { - keeper := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "short description", - AWS: &secretv0alpha1.AWSKeeperConfig{}, - Azure: &secretv0alpha1.AzureKeeperConfig{}, - GCP: &secretv0alpha1.GCPKeeperConfig{}, - HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{}, - }, - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("at least one `keeper` must be present", func(t *testing.T) { - keeper := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - }, - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("aws keeper validation", func(t *testing.T) { - validKeeperAWS := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - AWS: &secretv0alpha1.AWSKeeperConfig{ - AWSCredentials: secretv0alpha1.AWSCredentials{ - AccessKeyID: secretv0alpha1.CredentialValue{ - ValueFromEnv: "some-value", - }, - SecretAccessKey: secretv0alpha1.CredentialValue{ - SecureValueName: "some-value", - }, - KMSKeyID: "optional", - }, - }, - }, - } - - t.Run("`accessKeyId` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.accessKeyId", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.accessKeyId", errs[0].Field) - }) - }) - - t.Run("`secretAccessKey` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) - }) - }) - }) - - t.Run("azure keeper validation", func(t *testing.T) { - validKeeperAzure := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - Azure: &secretv0alpha1.AzureKeeperConfig{ - AzureCredentials: secretv0alpha1.AzureCredentials{ - KeyVaultName: "kv-name", - TenantID: "tenant-id", - ClientID: "client-id", - ClientSecret: secretv0alpha1.CredentialValue{ - ValueFromConfig: "config.path.value", - }, - }, - }, - }, - } - - t.Run("`keyVaultName` must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.KeyVaultName = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.keyVaultName", errs[0].Field) - }) - - t.Run("`tenantId` must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.TenantID = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.tenantId", errs[0].Field) - }) - - t.Run("`clientId` must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.ClientID = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.clientId", errs[0].Field) - }) - - t.Run("`clientSecret` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.clientSecret", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.clientSecret", errs[0].Field) - }) - }) - }) - - t.Run("gcp keeper validation", func(t *testing.T) { - validKeeperGCP := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - GCP: &secretv0alpha1.GCPKeeperConfig{ - GCPCredentials: secretv0alpha1.GCPCredentials{ - ProjectID: "project-id", - CredentialsFile: "/path/to/credentials/file.json", - }, - }, - }, - } - - t.Run("`projectId` must be present", func(t *testing.T) { - keeper := validKeeperGCP.DeepCopy() - keeper.Spec.GCP.ProjectID = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.gcp.projectId", errs[0].Field) - }) - - t.Run("`credentialsFile` must be present", func(t *testing.T) { - keeper := validKeeperGCP.DeepCopy() - keeper.Spec.GCP.CredentialsFile = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field) - }) - }) - - t.Run("hashicorp keeper validation", func(t *testing.T) { - validKeeperHashiCorp := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{ - HashiCorpCredentials: secretv0alpha1.HashiCorpCredentials{ - Address: "http://address", - Token: secretv0alpha1.CredentialValue{ - ValueFromConfig: "config.path.value", - }, - }, - }, - }, - } - - t.Run("`address` must be present", func(t *testing.T) { - keeper := validKeeperHashiCorp.DeepCopy() - keeper.Spec.HashiCorp.Address = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.hashicorp.address", errs[0].Field) - }) - - t.Run("`token` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperHashiCorp.DeepCopy() - keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.hashicorp.token", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperHashiCorp.DeepCopy() - keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.hashicorp.token", errs[0].Field) - }) - }) - }) -} diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest.go b/pkg/registry/apis/secret/reststorage/secure_value_rest.go deleted file mode 100644 index 7ea856509ea..00000000000 --- a/pkg/registry/apis/secret/reststorage/secure_value_rest.go +++ /dev/null @@ -1,379 +0,0 @@ -package reststorage - -import ( - "context" - "errors" - "fmt" - "strconv" - "strings" - - claims "github.com/grafana/authlib/types" - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" -) - -var ( - _ rest.Scoper = (*SecureValueRest)(nil) - _ rest.SingularNameProvider = (*SecureValueRest)(nil) - _ rest.Getter = (*SecureValueRest)(nil) - _ rest.Lister = (*SecureValueRest)(nil) - _ rest.Storage = (*SecureValueRest)(nil) - _ rest.Creater = (*SecureValueRest)(nil) - _ rest.Updater = (*SecureValueRest)(nil) - _ rest.GracefulDeleter = (*SecureValueRest)(nil) -) - -// SecureValueRest is an implementation of CRUDL operations on a `securevalue` backed by a persistence layer `store`. -type SecureValueRest struct { - storage contracts.SecureValueMetadataStorage - resource utils.ResourceInfo - tableConverter rest.TableConvertor -} - -// NewSecureValueRest is a returns a constructed `*SecureValueRest`. -func NewSecureValueRest(storage contracts.SecureValueMetadataStorage, resource utils.ResourceInfo) *SecureValueRest { - return &SecureValueRest{storage, resource, resource.TableConverter()} -} - -// New returns an empty `*SecureValue` that is used by the `Create` method. -func (s *SecureValueRest) New() runtime.Object { - return s.resource.NewFunc() -} - -// Destroy is called when? [TODO] -func (s *SecureValueRest) Destroy() {} - -// NamespaceScoped returns `true` because the storage is namespaced (== org). -func (s *SecureValueRest) NamespaceScoped() bool { - return true -} - -// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. -func (s *SecureValueRest) GetSingularName() string { - return s.resource.GetSingularName() -} - -// NewList returns an empty `*SecureValueList` that is used by the `List` method. -func (s *SecureValueRest) NewList() runtime.Object { - return s.resource.NewListFunc() -} - -// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. -func (s *SecureValueRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -// List calls the inner `store` (persistence) and returns a list of `securevalues` within a `namespace` filtered by the `options`. -func (s *SecureValueRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - secureValueList, err := s.storage.List(ctx, xkube.Namespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list secure values: %w", err) - } - - labelSelector := options.LabelSelector - - if labelSelector == nil { - labelSelector = labels.Everything() - } - - fieldSelector := options.FieldSelector - if fieldSelector == nil { - fieldSelector = fields.Everything() - } - - allowedSecureValues := make([]secretv0alpha1.SecureValue, 0, len(secureValueList)) - - for _, secureValue := range secureValueList { - // Filter by label - if labelSelector.Matches(labels.Set(secureValue.Labels)) { - // Filter by status.phase - if fieldSelector.Matches(fields.Set{"status.phase": string(secureValue.Status.Phase)}) { - allowedSecureValues = append(allowedSecureValues, secureValue) - } - } - } - - return &secretv0alpha1.SecureValueList{Items: allowedSecureValues}, nil -} - -// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`. -func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - sv, err := s.storage.Read(ctx, xkube.Namespace(namespace), name, contracts.ReadOpts{}) - if err != nil { - if errors.Is(err, contracts.ErrSecureValueNotFound) { - return nil, s.resource.NewNotFound(name) - } - - return nil, fmt.Errorf("failed to read secure value: %w", err) - } - - return sv, nil -} - -// Create a new `securevalue`. Does some validation and allows empty `name` (generated). -func (s *SecureValueRest) Create( - ctx context.Context, - obj runtime.Object, - createValidation rest.ValidateObjectFunc, - _ *metav1.CreateOptions, -) (runtime.Object, error) { - sv, ok := obj.(*secretv0alpha1.SecureValue) - if !ok { - return nil, fmt.Errorf("expected SecureValue for create") - } - - if err := createValidation(ctx, obj); err != nil { - return nil, err - } - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing auth info in context") - } - - createdSecureValueMetadata, err := s.storage.Create(ctx, sv, user.GetUID()) - if err != nil { - return nil, fmt.Errorf("creating secure value %+w", err) - } - - return createdSecureValueMetadata, nil -} - -// Update a `securevalue`'s `value`. The second return parameter indicates whether the resource was newly created. -// Currently does not support "create on update" functionality. If the securevalue does not yet exist, it returns an error. -func (s *SecureValueRest) Update( - ctx context.Context, - name string, - objInfo rest.UpdatedObjectInfo, - _ rest.ValidateObjectFunc, - updateValidation rest.ValidateObjectUpdateFunc, - _forceAllowCreate bool, - _ *metav1.UpdateOptions, -) (runtime.Object, bool, error) { - oldObj, err := s.Get(ctx, name, &metav1.GetOptions{}) - if err != nil { - return nil, false, err - } - - // Makes sure the UID and ResourceVersion are OK. - // TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually. - newObj, err := objInfo.UpdatedObject(ctx, oldObj) - if err != nil { - return nil, false, fmt.Errorf("k8s updated object: %w", err) - } - - if err := updateValidation(ctx, newObj, oldObj); err != nil { - return nil, false, err - } - - newSecureValue, ok := newObj.(*secretv0alpha1.SecureValue) - if !ok { - return nil, false, fmt.Errorf("expected SecureValue for update") - } - - // TODO: do we need to do this here again? Probably not, but double-check! - newSecureValue.Annotations = xkube.CleanAnnotations(newSecureValue.Annotations) - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing auth info in context") - } - - // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? - updatedSecureValueMetadata, err := s.storage.Update(ctx, newSecureValue, user.GetUID()) - if err != nil { - return updatedSecureValueMetadata, false, fmt.Errorf("updating secure value metadata: %+w", err) - } - - return updatedSecureValueMetadata, false, nil -} - -// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`. -func (s *SecureValueRest) Delete(ctx context.Context, name string, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions) (runtime.Object, bool, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing namespace") - } - - if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil { - if errors.Is(err, contracts.ErrSecureValueNotFound) { - return nil, false, s.resource.NewNotFound(name) - } - return nil, false, fmt.Errorf("deleting secure value: %+w", err) - } - - return nil, false, nil -} - -// ValidateSecureValue does basic spec validation of a securevalue. -func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admission.Operation, decryptersAllowList map[string]struct{}) field.ErrorList { - errs := make(field.ErrorList, 0) - - // Operation-specific field validation. - switch operation { - case admission.Create: - errs = validateSecureValueCreate(sv) - - // If we plan to support PATCH-style updates, we shouldn't be requiring fields to be set. - case admission.Update: - errs = validateSecureValueUpdate(sv, oldSv) - - case admission.Delete: - case admission.Connect: - } - - // General validations. - if len(sv.Spec.Value) > contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES { - errs = append( - errs, - field.TooLong(field.NewPath("spec", "value"), len(sv.Spec.Value), contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES), - ) - } - - if errs := validateDecrypters(sv.Spec.Decrypters, decryptersAllowList); len(errs) > 0 { - return errs - } - - return errs -} - -// validateSecureValueCreate does basic spec validation of a securevalue for the Create operation. -func validateSecureValueCreate(sv *secretv0alpha1.SecureValue) field.ErrorList { - errs := make(field.ErrorList, 0) - - if sv.Spec.Description == "" { - errs = append(errs, field.Required(field.NewPath("spec", "description"), "a `description` is required")) - } - - if sv.Spec.Value == "" && (sv.Spec.Ref == nil || (sv.Spec.Ref != nil && *sv.Spec.Ref == "")) { - errs = append(errs, field.Required(field.NewPath("spec"), "either a `value` or `ref` is required")) - } - - if sv.Spec.Value != "" && (sv.Spec.Ref != nil && *sv.Spec.Ref != "") { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "only one of `value` or `ref` can be set")) - } - - return errs -} - -// validateSecureValueUpdate does basic spec validation of a securevalue for the Update operation. -func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.ErrorList { - errs := make(field.ErrorList, 0) - - // For updates, an `old` object is required. - if oldSv == nil { - errs = append(errs, field.InternalError(field.NewPath("spec"), errors.New("old object is nil"))) - - return errs - } - - // Only validate if one of the fields is being changed/set. - if sv.Spec.Value != "" || (sv.Spec.Ref != nil && *sv.Spec.Ref != "") { - if (oldSv.Spec.Ref != nil && *oldSv.Spec.Ref != "") && sv.Spec.Value != "" { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `value` when `ref` was already previously set")) - } - - if (oldSv.Spec.Ref == nil || (oldSv.Spec.Ref != nil && *oldSv.Spec.Ref == "")) && (sv.Spec.Ref != nil && *sv.Spec.Ref != "") { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `ref` when `value` was already previously set")) - } - } - - // Keeper cannot be changed. - if sv.Spec.Keeper != oldSv.Spec.Keeper { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "the `keeper` cannot be changed")) - } - - return errs -} - -// validateDecrypters validates that (if populated) the `decrypters` must be unique. -func validateDecrypters(decrypters []string, decryptersAllowList map[string]struct{}) field.ErrorList { - errs := make(field.ErrorList, 0) - - // Limit the number of decrypters to 64 to not have it unbounded. - // The number was chosen arbitrarily and should be enough. - if len(decrypters) > 64 { - errs = append( - errs, - field.TooMany(field.NewPath("spec", "decrypters"), len(decrypters), 64), - ) - - return errs - } - - decrypterNames := make(map[string]struct{}, 0) - - for i, decrypter := range decrypters { - decrypter = strings.TrimSpace(decrypter) - if decrypter == "" { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters cannot be empty if specified"), - ) - - continue - } - - // Allow List: decrypters must match exactly and be in the allowed list to be able to decrypt. - if len(decryptersAllowList) > 0 { - if _, exists := decryptersAllowList[decrypter]; !exists { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, fmt.Sprintf("allowed values: %v", decryptersAllowList)), - ) - - return errs - } - - continue - } - - // Use the same validation as labels for the decrypters. - if verrs := validation.IsValidLabelValue(decrypter); len(verrs) > 0 { - for _, verr := range verrs { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, verr), - ) - } - - continue - } - - if _, exists := decrypterNames[decrypter]; exists { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters must be unique"), - ) - - continue - } - - decrypterNames[decrypter] = struct{}{} - } - - return errs -} diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go b/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go deleted file mode 100644 index 1656ca7a1fc..00000000000 --- a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package reststorage - -import ( - "fmt" - "maps" - "slices" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "k8s.io/apiserver/pkg/admission" - - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" -) - -func TestValidateSecureValue(t *testing.T) { - t.Run("when creating a new securevalue", func(t *testing.T) { - keeper := "keeper" - validSecureValue := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", - Value: "value", - Keeper: &keeper, - Decrypters: []string{"app1", "app2"}, - }, - } - - t.Run("the `description` must be present", func(t *testing.T) { - sv := validSecureValue.DeepCopy() - sv.Spec.Description = "" - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.description", errs[0].Field) - }) - - t.Run("either a `value` or `ref` must be present but not both", func(t *testing.T) { - sv := validSecureValue.DeepCopy() - sv.Spec.Value = "" - sv.Spec.Ref = nil - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - - ref := "value" - sv.Spec.Value = "value" - sv.Spec.Ref = &ref - - errs = ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("`value` cannot exceed 24576 bytes", func(t *testing.T) { - sv := validSecureValue.DeepCopy() - sv.Spec.Value = secretv0alpha1.NewExposedSecureValue(strings.Repeat("a", contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES+1)) - sv.Spec.Ref = nil - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.value", errs[0].Field) - }) - }) - - t.Run("when updating a securevalue", func(t *testing.T) { - t.Run("when trying to switch from a `value` (old) to a `ref` (new), it returns an error", func(t *testing.T) { - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: nil, // empty `ref` means a `value` was present. - }, - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: &ref, - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when trying to switch from a `ref` (old) to a `value` (new), it returns an error", func(t *testing.T) { - ref := "non-empty" - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: &ref, - }, - } - - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Value: "value", - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when both `value` and `ref` are set, it returns an error", func(t *testing.T) { - refNonEmpty := "non-empty" - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: &refNonEmpty, - }, - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Value: "value", - Ref: &ref, - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - - oldSv = &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Value: "non-empty", - }, - } - - errs = ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when no changes are made, it returns no errors", func(t *testing.T) { - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "old-description", - }, - } - - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "new-description", - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Empty(t, errs) - }) - - t.Run("when the old object is `nil` it returns an error", func(t *testing.T) { - sv := &secretv0alpha1.SecureValue{} - - errs := ValidateSecureValue(sv, nil, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when trying to change the `keeper`, it returns an error", func(t *testing.T) { - keeperA := "a-keeper" - keeperAnother := "another-keeper" - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Keeper: &keeperA, - }, - } - - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Keeper: &keeperAnother, - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - }) - - t.Run("`decrypters` must have unique items", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{ - "app1", - "app1", - }, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.decrypters.[1]", errs[0].Field) - }) - - t.Run("when set, the `decrypters` must be one of the allowed in the allow list", func(t *testing.T) { - allowList := map[string]struct{}{"app1": {}, "app2": {}} - decrypters := slices.Collect(maps.Keys(allowList)) - - t.Run("no matches, returns an error", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{"app3"}, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Len(t, errs, 1) - }) - - t.Run("no decrypters, returns no error", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{}, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Empty(t, errs) - }) - - t.Run("one match, returns no errors", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{decrypters[0]}, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Empty(t, errs) - }) - - t.Run("all matches, returns no errors", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: decrypters, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Empty(t, errs) - }) - }) - - t.Run("`decrypters` must be a valid label value", func(t *testing.T) { - decrypters := []string{ - "", // invalid - "is/this/valid", // invalid - "is this valid", // invalid - "is.this.valid", - "is-this-valid", - "is_this_valid", - "0isthisvalid9", - "isthisvalid9", - "0isthisvalid", - "isthisvalid", - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: decrypters, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 3) - }) - - t.Run("`decrypters` cannot have more than 64 items", func(t *testing.T) { - decrypters := make([]string, 0, 64+1) - for i := 0; i < 64+1; i++ { - decrypters = append(decrypters, fmt.Sprintf("app%d", i)) - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: decrypters, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.decrypters", errs[0].Field) - }) -} diff --git a/pkg/registry/apis/secret/secretkeeper/secretkeeper.go b/pkg/registry/apis/secret/secretkeeper/secretkeeper.go index 45bf6633106..3a580728f50 100644 --- a/pkg/registry/apis/secret/secretkeeper/secretkeeper.go +++ b/pkg/registry/apis/secret/secretkeeper/secretkeeper.go @@ -1,8 +1,9 @@ package secretkeeper import ( + "go.opentelemetry.io/otel/trace" + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" ) @@ -15,7 +16,7 @@ type OSSKeeperService struct { var _ contracts.KeeperService = (*OSSKeeperService)(nil) func ProvideService( - tracer tracing.Tracer, + tracer trace.Tracer, store contracts.EncryptedValueStorage, encryptionManager contracts.EncryptionManager, ) (*OSSKeeperService, error) { diff --git a/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go b/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go index aaa02e154e5..e48f8b95a93 100644 --- a/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go +++ b/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go @@ -5,8 +5,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" @@ -31,8 +31,10 @@ func Test_OSSKeeperService_GetKeepers(t *testing.T) { } func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error) { + tracer := noop.NewTracerProvider().Tracer("test") + // Initialize the keeper service - keeperService, err := ProvideService(tracing.InitializeTracerForTest(), nil, nil) + keeperService, err := ProvideService(tracer, nil, nil) return keeperService, err } diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go index 700abab63a5..04caae1237a 100644 --- a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go @@ -5,12 +5,13 @@ import ( "fmt" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) type SQLKeeper struct { - tracer tracing.Tracer + tracer trace.Tracer encryptionManager contracts.EncryptionManager store contracts.EncryptedValueStorage } @@ -18,7 +19,7 @@ type SQLKeeper struct { var _ contracts.Keeper = (*SQLKeeper)(nil) func NewSQLKeeper( - tracer tracing.Tracer, + tracer trace.Tracer, encryptionManager contracts.EncryptionManager, store contracts.EncryptedValueStorage, ) *SQLKeeper { @@ -31,7 +32,7 @@ func NewSQLKeeper( // TODO: parameter cfg is not being used func (s *SQLKeeper) Store(ctx context.Context, _ secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) { - ctx, span := s.tracer.Start(ctx, "sqlKeeper.Store") + ctx, span := s.tracer.Start(ctx, "SQLKeeper.Store", trace.WithAttributes(attribute.String("namespace", namespace))) defer span.End() encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef)) @@ -44,11 +45,18 @@ func (s *SQLKeeper) Store(ctx context.Context, _ secretv0alpha1.KeeperConfig, na return "", fmt.Errorf("unable to store encrypted value: %w", err) } - return contracts.ExternalID(encryptedVal.UID), nil + externalID := contracts.ExternalID(encryptedVal.UID) + + span.SetAttributes(attribute.String("externalID", externalID.String())) + + return externalID, nil } func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID) (secretv0alpha1.ExposedSecureValue, error) { - ctx, span := s.tracer.Start(ctx, "sqlKeeper.Expose") + ctx, span := s.tracer.Start(ctx, "SQLKeeper.Expose", trace.WithAttributes( + attribute.String("namespace", namespace), + attribute.String("externalID", externalID.String()), + )) defer span.End() encryptedValue, err := s.store.Get(ctx, namespace, externalID.String()) @@ -66,7 +74,10 @@ func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig, } func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID) error { - ctx, span := s.tracer.Start(ctx, "sqlKeeper.Delete") + ctx, span := s.tracer.Start(ctx, "SQLKeeper.Delete", trace.WithAttributes( + attribute.String("namespace", namespace), + attribute.String("externalID", externalID.String()), + )) defer span.End() err := s.store.Delete(ctx, namespace, externalID.String()) @@ -77,7 +88,10 @@ func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig, } func (s *SQLKeeper) Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID, exposedValueOrRef string) error { - ctx, span := s.tracer.Start(ctx, "sqlKeeper.Update") + ctx, span := s.tracer.Start(ctx, "SQLKeeper.Update", trace.WithAttributes( + attribute.String("namespace", namespace), + attribute.String("externalID", externalID.String()), + )) defer span.End() encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef)) diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go index b8a78e92dab..585b124941f 100644 --- a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/setting" ) @@ -136,6 +136,8 @@ func Test_SQLKeeperSetup(t *testing.T) { } func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) { + tracer := noop.NewTracerProvider().Tracer("test") + // Initialize the encryption manager with in-memory implementation encMgr := &inMemoryEncryptionManager{} @@ -143,7 +145,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) { encValueStore := newInMemoryEncryptedValueStorage() // Initialize the SQLKeeper - sqlKeeper := NewSQLKeeper(tracing.InitializeTracerForTest(), encMgr, encValueStore) + sqlKeeper := NewSQLKeeper(tracer, encMgr, encValueStore) return sqlKeeper, nil } diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index 7f8c1497111..ed7d0aa1c5e 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -46,6 +46,9 @@ var WireSet = wire.NewSet( wire.Bind(new(datasource.PluginContextWrapper), new(*plugincontext.Provider)), datasource.ProvideDefaultPluginConfigs, + // Secrets + secret.RegisterDependencies, + // Each must be added here *and* in the ServiceSink above dashboardinternal.RegisterAPIService, dashboardsnapshot.RegisterAPIService, @@ -58,7 +61,6 @@ var WireSet = wire.NewSet( provisioning.RegisterAPIService, service.RegisterAPIService, query.RegisterAPIService, - secret.RegisterAPIService, userstorage.RegisterAPIService, ofrep.RegisterAPIService, ) diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index efa495e511f..5657b0e4c14 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -732,20 +732,6 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer) - databaseDatabase := database5.ProvideDatabase(sqlStore) - secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - secretDBMigrator := migrator2.NewWithEngine(sqlStore) - secretAPIBuilder, err := secret.RegisterAPIService(featureToggles, cfg, apiserverService, tracingService, secureValueMetadataStorage, keeperMetadataStorage, accessClient, acimplService, secretDBMigrator) - if err != nil { - return nil, err - } factory := github.ProvideFactory() legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl) webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, factory, renderingService, resourceClient, eventualRestConfigProvider) @@ -759,7 +745,12 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } ofrepAPIBuilder := ofrep.RegisterAPIService(apiserverService, cfg, staticFlagEvaluator) - apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, secretAPIBuilder, apiBuilder, ofrepAPIBuilder) + secretDBMigrator := migrator2.NewWithEngine(sqlStore) + dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) + if err != nil { + return nil, err + } + apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, ofrepAPIBuilder, dependencyRegisterer) teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService) if err != nil { return nil, err @@ -1254,20 +1245,6 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer) - databaseDatabase := database5.ProvideDatabase(sqlStore) - secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - secretDBMigrator := migrator2.NewWithEngine(sqlStore) - secretAPIBuilder, err := secret.RegisterAPIService(featureToggles, cfg, apiserverService, tracingService, secureValueMetadataStorage, keeperMetadataStorage, accessClient, acimplService, secretDBMigrator) - if err != nil { - return nil, err - } factory := github.ProvideFactory() legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl) webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, factory, renderingService, resourceClient, eventualRestConfigProvider) @@ -1281,7 +1258,12 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } ofrepAPIBuilder := ofrep.RegisterAPIService(apiserverService, cfg, staticFlagEvaluator) - apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, secretAPIBuilder, apiBuilder, ofrepAPIBuilder) + secretDBMigrator := migrator2.NewWithEngine(sqlStore) + dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) + if err != nil { + return nil, err + } + apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, ofrepAPIBuilder, dependencyRegisterer) teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService) if err != nil { return nil, err diff --git a/pkg/services/authz/zanzana/README.md b/pkg/services/authz/zanzana/README.md index d3afff67a58..fdf7925a134 100644 --- a/pkg/services/authz/zanzana/README.md +++ b/pkg/services/authz/zanzana/README.md @@ -1,6 +1,251 @@ # Zanzana +Zanzana is authorization server and wrapper around OpenFGA. OpenFGA implements Zanzibar authorization model, which is relation-based access control. But it's pretty flexible, so you can use it for implementing various authorization models. + +## Running Zanzana in embedded mode + +By default Zanzana runs in the same binary as Grafana, it's called embedded mode. Grafana communicates with Zanzana/OpenFGA via in-proc GRPC. OpenFGA supports several DB types, like MySQL, Postgres, sqlite. Default is sqlite, but since we run Postgres in cloud, this is recommended setup. In case of Postgres and MySQL OpenFGA creates tables in the same database as grafana (default db name is "grafana"). Minimal config for running Zanzana with Postgres is this: + +```ini +app_mode = development + +[log] +level = info + +[feature_toggles] +zanzana = true + +[database] +type = postgres +host = 127.0.0.1:5432 +name = grafana +user = grafana +password = password +``` + +To run postgres DB you need to create docker compose file. Switch to the `devenv` directory and run script to create it: + +```sh +cd devenv +./create_docker_compose.sh postgres +docker compose up -d +``` + +or simply `make devenv sources=postgres` from repo root. + +Now you can run grafana (from source root directory): + +```sh +make run +``` + +## Instrumentation + +It's always good to know what happens inside and have tools to check performance and request path. So it's good idea to instrument grafana instance with metrics and traces. You can do it by running prometheus and tempo in docker and configure grafana to send traces. + +To run prometheus and tempo, add it to docker compose file: + +```sh +./create_docker_compose.sh postgres, tempo, grafana +``` + +There're some differences in compose files and some unnecessary blocks, so you can simply copy this docker-compose file: + +```yaml +services: + postgres: + image: postgres:15.7 + environment: + POSTGRES_USER: grafana + POSTGRES_PASSWORD: password + POSTGRES_DB: grafana + ports: + - "5432:5432" + command: postgres -c log_connections=on -c log_disconnections=on -c log_destination=stderr + healthcheck: + test: [ "CMD", "pg_isready", "-q", "-d", "grafana", "-U", "grafana" ] + timeout: 45s + interval: 10s + retries: 10 + + grafana: + image: grafana/grafana:latest + ports: + - "3001:3000" + volumes: + - "./dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml" + - "./datasources_docker.yaml:/etc/grafana/provisioning/datasources/datasources.yaml" + environment: + GF_RENDERING_SERVER_URL: http://renderer:8081/render + GF_RENDERING_CALLBACK_URL: http://grafana:3000/ + + prometheus: + image: prom/prometheus:latest + command: + - --config.file=/etc/prometheus.yaml + - --web.enable-remote-write-receiver + - --enable-feature=exemplar-storage + volumes: + - ./docker/blocks/prometheus/prometheus.yml:/etc/prometheus.yaml + ports: + - "9090:9090" + labels: + namespace: monitoring + logging: + driver: loki + options: + loki-url: 'http://localhost:3100/api/prom/push' + labels: namespace + + tempo: + image: grafana/tempo:latest + command: + - --config.file=/etc/tempo.yaml + volumes: + - ./docker/blocks/tempo/tempo.yaml:/etc/tempo.yaml + - ./docker/blocks/tempo/tempo-data:/tmp/tempo + ports: + - "14268:14268" # jaeger ingest + - "3200:3200" # tempo + - "4317:4317" # otlp grpc + - "4318:4318" # otlp http +``` + +```sh +docker-compose up -d +``` + +Then you need to configure grafana to send telemetry to Tempo: + +```ini +[server] +router_logging = true + +[tracing.opentelemetry.otlp] +address = localhost:4317 +``` + +Then start grafana. + +In order to use traces, you'll need to create Tempo data source in Grafana. Go to the grafana instance running in docker (http://localhost:3001) and create new Tempo data source with URL `http://tempo:3200`. Now you can use `gdev-prometheus` and `tempo` data sources for building dashboards or looking into metrics/traces in Explore. + +## Load testing + +For load testing you can use [grafana-api-tests](https://github.com/grafana/grafana-api-tests) repo. Clone it and navigate to `simulation/fake-user-generator` folder. Run `./generateNestedFolders.ts` script to populate data in grafana: + +```sh +./generateNestedFolders.ts --scenario medium -v --user admin --password +``` + +For the load testing run [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) and specify desired test: + +```sh +GRAFANA_PASSWORD= k6 run loadtest/tests/dashboard_by_uid.js +``` + +## Running Zanzana standalone server + +When running as a standalone server, Grafana communicates with Zanzana via GRPC. It also requires some additional GRPC authentication configuration. So you need to add auth-signer service to your docker compose file: + +```yaml + auth-signer: + build: ./docker/blocks/auth/signer/. + ports: + - "6481:8080" + volumes: + - ./docker/blocks/auth/signer/config.yaml:/app/config.yaml + restart: unless-stopped +``` + +This service is located in the grafana-enterprise repo, so make sure you have linked enterprise repo: + +```sh +# From OSS repo + make enterprise-to-oss + docker compose up -d + ``` + +Then add grafana configured in client mode to be able to test requests against zanzana server. Unfortunately, due to issue with config implementation, stack id cannot be configured through env variables, so you should create ini file and put it there: + +```ini +[environment] +stack_id = 11 +``` + +Then link it to the grafana client. Default stack id is `11` and token is `ThisIsMySecretToken`. Those values configured in `auth-signer` by default, so if you need to change it, follow instructions in [auth signer readme](https://github.com/grafana/grafana-enterprise/blob/main/src/devenv/blocks/auth/signer/README.md) + +```yaml + grafana-client: + image: grafana/grafana:main + ports: + - "3002:3000" + volumes: + - "./dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml" + - "./dev-dashboards:/usr/share/grafana/devenv/dev-dashboards" + - "./datasources_docker.yaml:/etc/grafana/provisioning/datasources/datasources.yaml" + - ":/etc/grafana/grafana.ini" + environment: + GF_DEFAULT_APP_MODE: development + GF_LOG_LEVEL: debug + GF_ENVIRONMENT_STACK_ID: 11 + GF_FEATURE_TOGGLES_ENABLE: zanzana authZGRPCServer unifiedStorage unifiedStorageSearch + GF_ZANZANA_CLIENT_MODE: client + GF_ZANZANA_CLIENT_ADDRESS: host.docker.internal:10000 + GF_ZANZANA_CLIENT_TOKEN: ThisIsMySecretToken + GF_ZANZANA_CLIENT_TOKEN_EXCHANGE_URL: http://host.docker.internal:6481/sign/access-token +``` + +Run containers: + +```sh + docker compose up -d + ``` + +Finally, configure zanzana standalone server (this is your `custom.ini` file in grafana repo): + +```ini +app_mode = development + +target = zanzana-server + +[log] +level = debug + +[feature_toggles] +zanzana = true + +[zanzana.server] +check_query_cache = true +signing_keys_url = http://localhost:6481/jwks + +[grpc_server] +enabled = true +address = 127.0.0.1:10000 + +[database] +type = postgres +host = 127.0.0.1:5432 +name = grafana +user = grafana +password = password + +[tracing.opentelemetry.otlp] +address = localhost:4317 +``` + +Now you can run zanzana server: + +```sh +make build-go +./bin//grafana server target +``` + +To test everything out, go to grafana client (http://localhost:3002) and open any dashboard. There should be some records in the zanzana server logs + +If you want to debug zanzana server, you can run it from VS Code debug panel - select `Run Authz server` target. Make sure that no grafana instances running at port `3001` since it's default port for this debug config (`docker compose stop grafana` if you have grafana running in docker, or remap it to another port). + ## Zanzana cli + Zanzana can be run as a standalone OpenFGA HTTP server that allows you to use the OpenFGA CLI to debug and manage fine-grained authorization relationships within Grafana. To test this you need to run standalone zanzana server. Use following config: @@ -14,6 +259,7 @@ target = zanzana-server zanzana = true [zanzana.server] +allow_insecure = true http_addr = 127.0.0.1:8080 [grpc_server] @@ -24,19 +270,19 @@ address = 127.0.0.1:10000 And then run grafana server target: ```bash -./bin/darwin-arm64/grafana server target +./bin//grafana server target ``` ### Using OpenFGA CLI -useful info on how to setup and use https://openfga.dev/docs/getting-started/cli -Once the server is running, you can interact with it using the OpenFGA CLI: +There's useful info on how to setup and use [OpenFGA CLI](https://openfga.dev/docs/getting-started/cli). Once the server is running, you can interact with it using the CLI: ```bash # List all stores fga store list # Other commands -fga model read -fga tuple list +fga model list --store-id +fga tuple read --store-id +fga query check ``` diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 417d95f0d52..fe6fe7d4fab 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -698,14 +698,11 @@ var ( Owner: grafanaDatavizSquad, }, { - // this is mainly used as a way to quickly disable query hints as a safeguard for our infrastructure - Name: "lokiQueryHints", - Description: "Enables query hints for Loki", - Stage: FeatureStageGeneralAvailability, - FrontendOnly: true, - Expression: "true", - Owner: grafanaObservabilityLogsSquad, - AllowSelfServe: false, + Name: "regressionTransformation", + Description: "Enables regression analysis transformation", + Stage: FeatureStagePublicPreview, + FrontendOnly: true, + Owner: grafanaDatavizSquad, }, { Name: "kubernetesFeatureToggles", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index bbd8023623b..5b44906dfc1 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -92,7 +92,7 @@ logsInfiniteScrolling,GA,@grafana/observability-logs,false,false,true logRowsPopoverMenu,GA,@grafana/observability-logs,false,false,true pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,false,false,false tableSharedCrosshair,experimental,@grafana/dataviz-squad,false,false,true -lokiQueryHints,GA,@grafana/observability-logs,false,false,true +regressionTransformation,preview,@grafana/dataviz-squad,false,false,true kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,false,false,true cloudRBACRoles,preview,@grafana/identity-access-team,false,true,false alertingQueryOptimization,GA,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 47d4562df37..bc513ac82d5 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -379,9 +379,9 @@ const ( // Enables shared crosshair in table panel FlagTableSharedCrosshair = "tableSharedCrosshair" - // FlagLokiQueryHints - // Enables query hints for Loki - FlagLokiQueryHints = "lokiQueryHints" + // FlagRegressionTransformation + // Enables regression analysis transformation + FlagRegressionTransformation = "regressionTransformation" // FlagKubernetesFeatureToggles // Use the kubernetes API for feature toggle management in the frontend diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index ce8ab6502f3..c1cb552b6bb 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1868,8 +1868,9 @@ { "metadata": { "name": "lokiQueryHints", - "resourceVersion": "1750434297879", - "creationTimestamp": "2023-12-18T20:43:16Z" + "resourceVersion": "1743693517832", + "creationTimestamp": "2023-12-18T20:43:16Z", + "deletionTimestamp": "2025-06-12T12:14:47Z" }, "spec": { "description": "Enables query hints for Loki", diff --git a/pkg/storage/secret/database/database.go b/pkg/storage/secret/database/database.go index 8997a24ee9e..ff990817e6a 100644 --- a/pkg/storage/secret/database/database.go +++ b/pkg/storage/secret/database/database.go @@ -6,6 +6,8 @@ import ( "errors" "github.com/jmoiron/sqlx" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" @@ -19,6 +21,7 @@ type contextSessionTxKey struct{} type Database struct { dbType string sqlx *sqlx.DB + tracer trace.Tracer // Keep the xorm.Engine instance and its references alive until the apiserver is shut down. // This is only needed because the xorm.Engine calls a runtime.SetFinalizer, in a RAII-like pattern to close the DB, @@ -34,13 +37,14 @@ type Database struct { engine *xorm.Engine } -func ProvideDatabase(db db.DB) *Database { +func ProvideDatabase(db db.DB, tracer trace.Tracer) *Database { engine := db.GetEngine() return &Database{ dbType: string(db.GetDBType()), sqlx: sqlx.NewDb(engine.DB().DB, db.GetDialect().DriverName()), engine: engine, + tracer: tracer, } } @@ -48,25 +52,31 @@ func (db *Database) DriverName() string { return db.dbType } -func (db *Database) Transaction(ctx context.Context, callback func(context.Context) error) error { - txCtx := ctx - +func (db *Database) Transaction(ctx context.Context, callback func(context.Context) error) (err error) { // If another transaction is already open, we just use that one instead of nesting. - sqlxTx, ok := txCtx.Value(contextSessionTxKey{}).(*sqlx.Tx) + sqlxTx, ok := ctx.Value(contextSessionTxKey{}).(*sqlx.Tx) if sqlxTx != nil && ok { // We are already in a transaction, so we don't commit or rollback, let the outermost transaction do it. - return callback(txCtx) + return callback(ctx) } - tx, err := db.sqlx.Beginx() + spanCtx, span := db.tracer.Start(ctx, "Database.Transaction") + defer span.End() + + defer func() { + if err != nil { + span.SetStatus(codes.Error, "Transaction failed") + span.RecordError(err) + } + }() + + sqlxTx, err = db.sqlx.BeginTxx(spanCtx, nil) if err != nil { return err } - sqlxTx = tx - // Save it in the context so the transaction can be reused in case it is nested. - txCtx = context.WithValue(ctx, contextSessionTxKey{}, sqlxTx) + txCtx := context.WithValue(spanCtx, contextSessionTxKey{}, sqlxTx) if err := callback(txCtx); err != nil { if rbErr := sqlxTx.Rollback(); rbErr != nil { @@ -80,19 +90,25 @@ func (db *Database) Transaction(ctx context.Context, callback func(context.Conte } func (db *Database) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) { + spanCtx, span := db.tracer.Start(ctx, "Database.ExecContext") + defer span.End() + // If another transaction is already open, we just use that one instead of nesting. if tx, ok := ctx.Value(contextSessionTxKey{}).(*sqlx.Tx); tx != nil && ok { - return tx.ExecContext(ctx, db.sqlx.Rebind(query), args...) + return tx.ExecContext(spanCtx, db.sqlx.Rebind(query), args...) } - return db.sqlx.ExecContext(ctx, db.sqlx.Rebind(query), args...) + return db.sqlx.ExecContext(spanCtx, db.sqlx.Rebind(query), args...) } func (db *Database) QueryContext(ctx context.Context, query string, args ...any) (contracts.Rows, error) { + spanCtx, span := db.tracer.Start(ctx, "Database.QueryContext") + defer span.End() + // If another transaction is already open, we just use that one instead of nesting. if tx, ok := ctx.Value(contextSessionTxKey{}).(*sqlx.Tx); tx != nil && ok { - return tx.QueryContext(ctx, db.sqlx.Rebind(query), args...) + return tx.QueryContext(spanCtx, db.sqlx.Rebind(query), args...) } - return db.sqlx.QueryContext(ctx, db.sqlx.Rebind(query), args...) + return db.sqlx.QueryContext(spanCtx, db.sqlx.Rebind(query), args...) } diff --git a/pkg/storage/secret/encryption/data_key_store.go b/pkg/storage/secret/encryption/data_key_store.go index 15b7dfe2f4d..c38c4ce1153 100644 --- a/pkg/storage/secret/encryption/data_key_store.go +++ b/pkg/storage/secret/encryption/data_key_store.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" - "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) @@ -22,12 +21,7 @@ type encryptionStoreImpl struct { log log.Logger } -func ProvideDataKeyStorage( - db contracts.Database, - tracer trace.Tracer, - features featuremgmt.FeatureToggles, - registerer prometheus.Registerer, -) (contracts.DataKeyStorage, error) { +func ProvideDataKeyStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.DataKeyStorage, error) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { return &encryptionStoreImpl{}, nil diff --git a/pkg/storage/secret/encryption/data_key_store_test.go b/pkg/storage/secret/encryption/data_key_store_test.go index 2bbbecbf5a6..450577f4584 100644 --- a/pkg/storage/secret/encryption/data_key_store_test.go +++ b/pkg/storage/secret/encryption/data_key_store_test.go @@ -31,7 +31,7 @@ func TestEncryptionStoreImpl_DataKeyLifecycle(t *testing.T) { testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) tracer := noop.NewTracerProvider().Tracer("test") features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) - store, err := ProvideDataKeyStorage(database.ProvideDatabase(testDB), tracer, features, nil) + store, err := ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features) require.NoError(t, err) ctx := context.Background() diff --git a/pkg/storage/secret/encryption/encrypted_value_store.go b/pkg/storage/secret/encryption/encrypted_value_store.go index 55ab5eb54ab..58bdd4054a2 100644 --- a/pkg/storage/secret/encryption/encrypted_value_store.go +++ b/pkg/storage/secret/encryption/encrypted_value_store.go @@ -7,6 +7,8 @@ import ( "time" "github.com/google/uuid" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -17,7 +19,7 @@ var ( ErrEncryptedValueNotFound = errors.New("encrypted value not found") ) -func ProvideEncryptedValueStorage(db contracts.Database, features featuremgmt.FeatureToggles) (contracts.EncryptedValueStorage, error) { +func ProvideEncryptedValueStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.EncryptedValueStorage, error) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { return &encryptedValStorage{}, nil @@ -26,15 +28,28 @@ func ProvideEncryptedValueStorage(db contracts.Database, features featuremgmt.Fe return &encryptedValStorage{ db: db, dialect: sqltemplate.DialectForDriver(db.DriverName()), + tracer: tracer, }, nil } type encryptedValStorage struct { db contracts.Database dialect sqltemplate.Dialect + tracer trace.Tracer } -func (s *encryptedValStorage) Create(ctx context.Context, namespace string, encryptedData []byte) (*contracts.EncryptedValue, error) { +func (s *encryptedValStorage) Create(ctx context.Context, namespace string, encryptedData []byte) (ev *contracts.EncryptedValue, err error) { + ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Create", trace.WithAttributes( + attribute.String("namespace", namespace), + )) + defer span.End() + + defer func() { + if ev != nil { + span.SetAttributes(attribute.String("uid", ev.UID)) + } + }() + createdTime := time.Now().Unix() encryptedValue := &EncryptedValue{ UID: uuid.New().String(), @@ -74,6 +89,12 @@ func (s *encryptedValStorage) Create(ctx context.Context, namespace string, encr } func (s *encryptedValStorage) Update(ctx context.Context, namespace string, uid string, encryptedData []byte) error { + ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Update", trace.WithAttributes( + attribute.String("uid", uid), + attribute.String("namespace", namespace), + )) + defer span.End() + req := updateEncryptedValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace, @@ -102,6 +123,12 @@ func (s *encryptedValStorage) Update(ctx context.Context, namespace string, uid } func (s *encryptedValStorage) Get(ctx context.Context, namespace string, uid string) (*contracts.EncryptedValue, error) { + ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Get", trace.WithAttributes( + attribute.String("uid", uid), + attribute.String("namespace", namespace), + )) + defer span.End() + req := &readEncryptedValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace, @@ -141,6 +168,12 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace string, uid str } func (s *encryptedValStorage) Delete(ctx context.Context, namespace string, uid string) error { + ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Delete", trace.WithAttributes( + attribute.String("uid", uid), + attribute.String("namespace", namespace), + )) + defer span.End() + req := deleteEncryptedValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace, diff --git a/pkg/storage/secret/encryption/encrypted_value_store_test.go b/pkg/storage/secret/encryption/encrypted_value_store_test.go index 71b5c4c5ae5..92afe58e7e2 100644 --- a/pkg/storage/secret/encryption/encrypted_value_store_test.go +++ b/pkg/storage/secret/encryption/encrypted_value_store_test.go @@ -9,16 +9,18 @@ import ( "github.com/grafana/grafana/pkg/storage/secret/database" "github.com/grafana/grafana/pkg/storage/secret/migrator" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" ) func TestEncryptedValueStoreImpl(t *testing.T) { // Initialize data key storage with a fake db testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) - database := database.ProvideDatabase(testDB) + tracer := noop.NewTracerProvider().Tracer("test") + database := database.ProvideDatabase(testDB, tracer) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) ctx := context.Background() - store, err := ProvideEncryptedValueStorage(database, features) + store, err := ProvideEncryptedValueStorage(database, tracer, features) require.NoError(t, err) t.Run("creating an encrypted value returns it", func(t *testing.T) { diff --git a/pkg/storage/secret/metadata/keeper_store.go b/pkg/storage/secret/metadata/keeper_store.go index 78458201e89..89b115eef2c 100644 --- a/pkg/storage/secret/metadata/keeper_store.go +++ b/pkg/storage/secret/metadata/keeper_store.go @@ -9,17 +9,21 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) // keeperMetadataStorage is the actual implementation of the keeper metadata storage. type keeperMetadataStorage struct { db contracts.Database dialect sqltemplate.Dialect + tracer trace.Tracer } var _ contracts.KeeperMetadataStorage = (*keeperMetadataStorage)(nil) -func ProvideKeeperMetadataStorage(db contracts.Database, features featuremgmt.FeatureToggles) (contracts.KeeperMetadataStorage, error) { +func ProvideKeeperMetadataStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.KeeperMetadataStorage, error) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { return &keeperMetadataStorage{}, nil @@ -28,10 +32,18 @@ func ProvideKeeperMetadataStorage(db contracts.Database, features featuremgmt.Fe return &keeperMetadataStorage{ db: db, dialect: sqltemplate.DialectForDriver(db.DriverName()), + tracer: tracer, }, nil } func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Create", trace.WithAttributes( + attribute.String("name", keeper.GetName()), + attribute.String("namespace", keeper.GetNamespace()), + attribute.String("actorUID", actorUID), + )) + defer span.End() + row, err := toKeeperCreateRow(keeper, actorUID) if err != nil { return nil, fmt.Errorf("failed to create row: %w", err) @@ -82,6 +94,13 @@ func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alph } func (s *keeperMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.Keeper, error) { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Read", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + attribute.Bool("isForUpdate", opts.ForUpdate), + )) + defer span.End() + keeperDB, err := s.read(ctx, namespace.String(), name, opts) if err != nil { return nil, err @@ -134,6 +153,13 @@ func (s *keeperMetadataStorage) read(ctx context.Context, namespace, name string } func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Update", trace.WithAttributes( + attribute.String("name", newKeeper.GetName()), + attribute.String("namespace", newKeeper.GetNamespace()), + attribute.String("actorUID", actorUID), + )) + defer span.End() + var newRow *keeperDB err := s.db.Transaction(ctx, func(ctx context.Context) error { @@ -195,6 +221,12 @@ func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0a } func (s *keeperMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Delete", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + )) + defer span.End() + req := deleteKeeper{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), @@ -224,7 +256,16 @@ func (s *keeperMetadataStorage) Delete(ctx context.Context, namespace xkube.Name return nil } -func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.Keeper, error) { +func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (keeperList []secretv0alpha1.Keeper, err error) { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.List", trace.WithAttributes( + attribute.String("namespace", namespace.String()), + )) + defer span.End() + + defer func() { + span.SetAttributes(attribute.Int("returnedList.count", len(keeperList))) + }() + req := listKeeper{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), @@ -270,7 +311,20 @@ func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namesp // validateSecureValueReferences checks that all secure values referenced by the keeper exist and are not referenced by other third-party keepers. // It is used by other methods inside a transaction. -func (s *keeperMetadataStorage) validateSecureValueReferences(ctx context.Context, keeper *secretv0alpha1.Keeper) error { +func (s *keeperMetadataStorage) validateSecureValueReferences(ctx context.Context, keeper *secretv0alpha1.Keeper) (err error) { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.ValidateSecureValueReferences", trace.WithAttributes( + attribute.String("name", keeper.GetName()), + attribute.String("namespace", keeper.GetNamespace()), + )) + defer span.End() + + defer func() { + if err != nil { + span.SetStatus(codes.Error, "failed to validate secure value references") + span.RecordError(err) + } + }() + usedSecureValues := extractSecureValues(keeper) // No secure values are referenced, return early. @@ -405,11 +459,19 @@ func (s *keeperMetadataStorage) validateSecureValueReferences(ctx context.Contex } func (s *keeperMetadataStorage) GetKeeperConfig(ctx context.Context, namespace string, name *string, opts contracts.ReadOpts) (secretv0alpha1.KeeperConfig, error) { + ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.GetKeeperConfig", trace.WithAttributes( + attribute.String("namespace", namespace), + attribute.Bool("isForUpdate", opts.ForUpdate), + )) + defer span.End() + // Check if keeper is the systemwide one. if name == nil { return nil, nil } + span.SetAttributes(attribute.String("name", *name)) + // Load keeper config from metadata store, or TODO: keeper cache. kp, err := s.read(ctx, namespace, *name, opts) if err != nil { diff --git a/pkg/storage/secret/metadata/keeper_store_test.go b/pkg/storage/secret/metadata/keeper_store_test.go index 698697cdbe9..a2165d5273f 100644 --- a/pkg/storage/secret/metadata/keeper_store_test.go +++ b/pkg/storage/secret/metadata/keeper_store_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/storage/secret/database" "github.com/grafana/grafana/pkg/storage/secret/migrator" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" ) func Test_KeeperMetadataStorage_GetKeeperConfig(t *testing.T) { @@ -334,11 +335,12 @@ func Test_KeeperMetadataStorage_GetKeeperConfig(t *testing.T) { func initStorage(t *testing.T) contracts.KeeperMetadataStorage { testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) - db := database.ProvideDatabase(testDB) + tracer := noop.NewTracerProvider().Tracer("test") + db := database.ProvideDatabase(testDB, tracer) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) // Initialize the keeper storage - keeperMetadataStorage, err := ProvideKeeperMetadataStorage(db, features) + keeperMetadataStorage, err := ProvideKeeperMetadataStorage(db, tracer, features) require.NoError(t, err) return keeperMetadataStorage } diff --git a/pkg/storage/secret/metadata/outbox_store.go b/pkg/storage/secret/metadata/outbox_store.go index 81acd6343bf..f7a67e74c68 100644 --- a/pkg/storage/secret/metadata/outbox_store.go +++ b/pkg/storage/secret/metadata/outbox_store.go @@ -7,6 +7,9 @@ import ( "time" unifiedsql "github.com/grafana/grafana/pkg/storage/unified/sql" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "github.com/google/uuid" "github.com/grafana/grafana/pkg/registry/apis/secret/assert" @@ -17,12 +20,14 @@ import ( type outboxStore struct { db contracts.Database dialect sqltemplate.Dialect + tracer trace.Tracer } -func ProvideOutboxQueue(db contracts.Database) contracts.OutboxQueue { +func ProvideOutboxQueue(db contracts.Database, tracer trace.Tracer) contracts.OutboxQueue { return &outboxStore{ db: db, dialect: sqltemplate.DialectForDriver(db.DriverName()), + tracer: tracer, } } @@ -39,10 +44,29 @@ type outboxMessageDB struct { Created int64 } -func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMessage) (string, error) { +func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMessage) (messageID string, err error) { + ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes( + attribute.String("name", input.Name), + attribute.String("namespace", input.Namespace), + attribute.String("type", string(input.Type)), + attribute.String("requestID", input.RequestID), + )) + defer span.End() + + defer func() { + if err != nil { + span.SetStatus(codes.Error, "failed to append outbox message") + span.RecordError(err) + } + + if messageID != "" { + span.SetAttributes(attribute.String("messageID", messageID)) + } + }() + assert.True(input.Type != "", "outboxStore.Append: outbox message type is required") - messageID, err := s.insertMessage(ctx, input) + messageID, err = s.insertMessage(ctx, input) if err != nil { return messageID, fmt.Errorf("inserting message into outbox table: %+w", err) } @@ -189,7 +213,19 @@ func (s *outboxStore) ReceiveN(ctx context.Context, n uint) ([]contracts.OutboxM return messages, nil } -func (s *outboxStore) Delete(ctx context.Context, messageID string) error { +func (s *outboxStore) Delete(ctx context.Context, messageID string) (err error) { + ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes( + attribute.String("messageID", messageID), + )) + defer span.End() + + defer func() { + if err != nil { + span.SetStatus(codes.Error, "failed to delete message from outbox") + span.RecordError(err) + } + }() + assert.True(messageID != "", "outboxStore.Delete: messageID is required") if err := s.deleteMessage(ctx, messageID); err != nil { diff --git a/pkg/storage/secret/metadata/outbox_store_test.go b/pkg/storage/secret/metadata/outbox_store_test.go index 5bbf2fcea70..0dd6c024d98 100644 --- a/pkg/storage/secret/metadata/outbox_store_test.go +++ b/pkg/storage/secret/metadata/outbox_store_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/storage/secret/database" "github.com/grafana/grafana/pkg/storage/secret/migrator" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" ) type outboxStoreModel struct { @@ -110,10 +111,11 @@ func TestOutboxStoreSecureValueOperationInProgress(t *testing.T) { t.Parallel() testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) + tracer := noop.NewTracerProvider().Tracer("test") ctx := context.Background() - outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB)) + outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer) _, err := outbox.Append(ctx, contracts.AppendOutboxMessage{ RequestID: "1", @@ -142,10 +144,11 @@ func TestOutboxStoreSecureValueOperationInProgress(t *testing.T) { func TestOutboxStore(t *testing.T) { testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) + tracer := noop.NewTracerProvider().Tracer("test") ctx := context.Background() - outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB)) + outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer) m1 := contracts.AppendOutboxMessage{ Type: contracts.CreateSecretOutboxMessage, @@ -212,8 +215,9 @@ func TestOutboxStoreProperty(t *testing.T) { // The number of iterations was decided arbitrarily based on the time the test takes to run for range 10 { testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) + tracer := noop.NewTracerProvider().Tracer("test") - outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB)) + outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer) model := newOutboxStoreModel() diff --git a/pkg/storage/secret/metadata/secure_value_store.go b/pkg/storage/secret/metadata/secure_value_store.go index eaa50571cec..39f42dcbdbe 100644 --- a/pkg/storage/secret/metadata/secure_value_store.go +++ b/pkg/storage/secret/metadata/secure_value_store.go @@ -10,11 +10,13 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/storage/unified/sql" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) var _ contracts.SecureValueMetadataStorage = (*secureValueMetadataStorage)(nil) -func ProvideSecureValueMetadataStorage(db contracts.Database, features featuremgmt.FeatureToggles) (contracts.SecureValueMetadataStorage, error) { +func ProvideSecureValueMetadataStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.SecureValueMetadataStorage, error) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { return &secureValueMetadataStorage{}, nil @@ -23,6 +25,7 @@ func ProvideSecureValueMetadataStorage(db contracts.Database, features featuremg return &secureValueMetadataStorage{ db: db, dialect: sqltemplate.DialectForDriver(db.DriverName()), + tracer: tracer, }, nil } @@ -30,9 +33,17 @@ func ProvideSecureValueMetadataStorage(db contracts.Database, features featuremg type secureValueMetadataStorage struct { db contracts.Database dialect sqltemplate.Dialect + tracer trace.Tracer } func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Create", trace.WithAttributes( + attribute.String("name", sv.GetName()), + attribute.String("namespace", sv.GetNamespace()), + attribute.String("actorUID", actorUID), + )) + defer span.End() + sv.Status.Phase = secretv0alpha1.SecureValuePhasePending sv.Status.Message = "Creating secure value" @@ -110,6 +121,13 @@ func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alp } func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.SecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Read", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + attribute.Bool("isForUpdate", opts.ForUpdate), + )) + defer span.End() + secureValue, err := s.read(ctx, namespace, name, opts) if err != nil { return nil, err @@ -124,6 +142,13 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N } func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Update", trace.WithAttributes( + attribute.String("name", newSecureValue.GetName()), + attribute.String("namespace", newSecureValue.GetNamespace()), + attribute.String("actorUID", actorUID), + )) + defer span.End() + var newRow *secureValueDB err := s.db.Transaction(ctx, func(ctx context.Context) error { @@ -207,6 +232,12 @@ func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue } func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Delete", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + )) + defer span.End() + req := deleteSecureValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), @@ -230,7 +261,16 @@ func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube return nil } -func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.SecureValue, error) { +func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (svList []secretv0alpha1.SecureValue, error error) { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.List", trace.WithAttributes( + attribute.String("namespace", namespace.String()), + )) + defer span.End() + + defer func() { + span.SetAttributes(attribute.Int("returnedList.count", len(svList))) + }() + req := listSecureValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), @@ -280,6 +320,13 @@ func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.N } func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID contracts.ExternalID) error { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetExternalID", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + attribute.String("externalID", externalID.String()), + )) + defer span.End() + req := updateExternalIdSecureValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), @@ -309,6 +356,15 @@ func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespac } func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetStatus", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + attribute.String("status.phase", string(status.Phase)), + attribute.String("status.message", status.Message), + attribute.String("status.externalID", status.ExternalID), + )) + defer span.End() + req := updateStatusSecureValue{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), @@ -339,6 +395,12 @@ func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xk } func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) { + ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.ReadForDecrypt", trace.WithAttributes( + attribute.String("name", name), + attribute.String("namespace", namespace.String()), + )) + defer span.End() + req := readSecureValueForDecrypt{ SQLTemplate: sqltemplate.New(s.dialect), Namespace: namespace.String(), diff --git a/pkg/storage/secret/metadata/secure_value_store_test.go b/pkg/storage/secret/metadata/secure_value_store_test.go index 01334ae4bd3..6c710f0877d 100644 --- a/pkg/storage/secret/metadata/secure_value_store_test.go +++ b/pkg/storage/secret/metadata/secure_value_store_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/storage/secret/database" "github.com/grafana/grafana/pkg/storage/secret/migrator" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" ) func createTestKeeper(t *testing.T, ctx context.Context, keeperStorage contracts.KeeperMetadataStorage, name, namespace string) string { @@ -36,16 +37,17 @@ func createTestKeeper(t *testing.T, ctx context.Context, keeperStorage contracts func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) { ctx := context.Background() testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) - db := database.ProvideDatabase(testDB) + tracer := noop.NewTracerProvider().Tracer("test") + db := database.ProvideDatabase(testDB, tracer) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) // Initialize the secure value storage - secureValueStorage, err := ProvideSecureValueMetadataStorage(db, features) + secureValueStorage, err := ProvideSecureValueMetadataStorage(db, tracer, features) require.NoError(t, err) // Initialize the keeper storage - keeperStorage, err := ProvideKeeperMetadataStorage(db, features) + keeperStorage, err := ProvideKeeperMetadataStorage(db, tracer, features) require.NoError(t, err) t.Run("create and read a secure value", func(t *testing.T) { diff --git a/pkg/storage/unified/resource/event.go b/pkg/storage/unified/resource/event.go index 4a1ceb06a21..4d1edb5d27d 100644 --- a/pkg/storage/unified/resource/event.go +++ b/pkg/storage/unified/resource/event.go @@ -2,6 +2,7 @@ package resource import ( "context" + "fmt" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" @@ -26,6 +27,26 @@ type WriteEvent struct { ObjectOld utils.GrafanaMetaAccessor } +func (e *WriteEvent) Validate() error { + if e.Object == nil { + return fmt.Errorf("object is nil") + } + + if e.Key == nil { + return fmt.Errorf("key is nil") + } + + if e.Value == nil { + return fmt.Errorf("value is nil") + } + + if e.Type == resourcepb.WatchEvent_UNKNOWN { + return fmt.Errorf("watch event type is unknown") + } + + return nil +} + // WrittenEvent is a WriteEvent reported with a resource version. type WrittenEvent struct { Type resourcepb.WatchEvent_Type diff --git a/pkg/storage/unified/resource/kv_test.go b/pkg/storage/unified/resource/kv_test.go index a999495d5f7..8ecf6a54751 100644 --- a/pkg/storage/unified/resource/kv_test.go +++ b/pkg/storage/unified/resource/kv_test.go @@ -1,15 +1,13 @@ package resource import ( - "bytes" "context" - "fmt" "io" + "strings" "testing" badger "github.com/dgraph-io/badger/v4" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,139 +25,9 @@ func setupTestBadgerDB(t *testing.T) *badger.DB { func setupTestKV(t *testing.T) KV { db := setupTestBadgerDB(t) - t.Cleanup(func() { - err := db.Close() - require.NoError(t, err) - }) return NewBadgerKV(db) } -func TestBadgerKV_Get(t *testing.T) { - db := setupTestBadgerDB(t) - - kv := NewBadgerKV(db) - ctx := context.Background() - - // Setup test data - err := db.Update(func(txn *badger.Txn) error { - return txn.Set([]byte("section/key1"), []byte("value1")) - }) - require.NoError(t, err) - - t.Run("Get existing key", func(t *testing.T) { - obj, err := kv.Get(ctx, "section", "key1") - require.NoError(t, err) - assert.Equal(t, "key1", obj.Key) - - // Read the value from the Reader - value, err := io.ReadAll(obj.Value) - require.NoError(t, err) - assert.Equal(t, []byte("value1"), value) - }) - - t.Run("Get non-existent key", func(t *testing.T) { - _, err := kv.Get(ctx, "section", "nonexistent") - assert.Error(t, err) - assert.Equal(t, ErrNotFound, err) - }) -} - -func TestBadgerKV_Save(t *testing.T) { - db := setupTestBadgerDB(t) - - kv := NewBadgerKV(db) - ctx := context.Background() - - t.Run("Save new key", func(t *testing.T) { - err := kv.Save(ctx, "section", "key1", bytes.NewReader([]byte("value1"))) - require.NoError(t, err) - - // Verify the value was saved - obj, err := kv.Get(ctx, "section", "key1") - require.NoError(t, err) - assert.Equal(t, "key1", obj.Key) - - value, err := io.ReadAll(obj.Value) - require.NoError(t, err) - assert.Equal(t, []byte("value1"), value) - }) - - t.Run("Save overwrite existing key", func(t *testing.T) { - // First save - err := kv.Save(ctx, "section", "key1", bytes.NewReader([]byte("oldvalue"))) - require.NoError(t, err) - - // Overwrite - err = kv.Save(ctx, "section", "key1", bytes.NewReader([]byte("newvalue"))) - require.NoError(t, err) - - // Verify the value was updated - obj, err := kv.Get(ctx, "section", "key1") - require.NoError(t, err) - assert.Equal(t, "key1", obj.Key) - - value, err := io.ReadAll(obj.Value) - require.NoError(t, err) - assert.Equal(t, []byte("newvalue"), value) - }) -} - -func TestBadgerKV_Delete(t *testing.T) { - db := setupTestBadgerDB(t) - - kv := NewBadgerKV(db) - ctx := context.Background() - - t.Run("Delete existing key", func(t *testing.T) { - // First create a key - err := kv.Save(ctx, "section", "key1", bytes.NewReader([]byte("value1"))) - require.NoError(t, err) - - // Delete it - err = kv.Delete(ctx, "section", "key1") - require.NoError(t, err) - - // Verify it's gone - _, err = kv.Get(ctx, "section", "key1") - assert.Error(t, err) - assert.Equal(t, ErrNotFound, err) - }) - - t.Run("Delete non-existent key", func(t *testing.T) { - err := kv.Delete(ctx, "section", "nonexistent") - assert.Error(t, err) - assert.Equal(t, ErrNotFound, err) - }) -} - -// setupIteratorTestData creates a test environment with common test data -func setupIteratorTestData(t *testing.T) (*badgerKV, context.Context) { - db := setupTestBadgerDB(t) - t.Cleanup(func() { - err := db.Close() - require.NoError(t, err) - }) - - kv := NewBadgerKV(db) - ctx := context.Background() - - // Setup test data - keys := []string{"a1", "a2", "b1", "b2", "c1"} - for _, k := range keys { - err := kv.Save(ctx, "section", k, bytes.NewReader([]byte("value"+k))) - require.NoError(t, err) - } - - return kv, ctx -} - -// iteratorTestCase represents a test case for iteration methods -type iteratorTestCase struct { - name string - options ListOptions - expectedKeys []string -} - func TestPrefixRangeEnd(t *testing.T) { require.Equal(t, "b", PrefixRangeEnd("a")) require.Equal(t, "a/c", PrefixRangeEnd("a/b")) @@ -167,95 +35,190 @@ func TestPrefixRangeEnd(t *testing.T) { require.Equal(t, "", PrefixRangeEnd("")) } -func TestBadgerKV_Keys(t *testing.T) { - for _, tc := range []iteratorTestCase{ - { - name: "all items", - options: ListOptions{}, - expectedKeys: []string{"a1", "a2", "b1", "b2", "c1"}, - }, - { - name: "with limit", - options: ListOptions{Limit: 2}, - expectedKeys: []string{"a1", "a2"}, - }, - { - name: "with range", - options: ListOptions{StartKey: "a", EndKey: "b"}, - expectedKeys: []string{"a1", "a2"}, - }, - { - name: "with prefix", - options: ListOptions{StartKey: "a", EndKey: PrefixRangeEnd("a")}, - expectedKeys: []string{"a1", "a2"}, - }, - { - name: "in descending order", - options: ListOptions{Sort: SortOrderDesc}, - expectedKeys: []string{"c1", "b2", "b1", "a2", "a1"}, - }, - { - name: "in descending order with prefix", - options: ListOptions{StartKey: "a", EndKey: PrefixRangeEnd("a"), Sort: SortOrderDesc}, - expectedKeys: []string{"a2", "a1"}, - }, - } { - t.Run("Keys "+tc.name, func(t *testing.T) { - kv, ctx := setupIteratorTestData(t) +func TestBadgerKVSmoke(t *testing.T) { + // Simple smoke test to ensure the basic badger KV implementation works + kv := setupTestKV(t) + ctx := context.Background() - var keys []string - for k, err := range kv.Keys(ctx, "section", tc.options) { - require.NoError(t, err) - keys = append(keys, k) - } - assert.Equal(t, tc.expectedKeys, keys) - }) - } + // Test unix timestamp works + timestamp, err := kv.UnixTimestamp(ctx) + require.NoError(t, err) + require.Greater(t, timestamp, int64(0)) + + // Test get non-existent key returns proper error + _, err = kv.Get(ctx, "test-section", "non-existent") + require.Error(t, err) + require.Equal(t, ErrNotFound, err) } -func TestBadgerKV_Concurrent(t *testing.T) { +func TestBadgerKV_UnderlyingStorage(t *testing.T) { + // Test internal key storage format and structure db := setupTestBadgerDB(t) - kv := NewBadgerKV(db) ctx := context.Background() - t.Run("Concurrent operations", func(t *testing.T) { - const numGoroutines = 10 - done := make(chan struct{}) + t.Run("keys are stored with section prefix", func(t *testing.T) { + section := "test-section" + key := "test-key" + value := "test-value" + expectedInternalKey := section + "/" + key - for i := 0; i < numGoroutines; i++ { - go func(i int) { - defer func() { done <- struct{}{} }() + // Save through KV interface + err := kv.Save(ctx, section, key, strings.NewReader(value)) + require.NoError(t, err) - key := fmt.Sprintf("key%d", i) - value := []byte(fmt.Sprintf("value%d", i)) + // Verify the raw key exists in badger with correct format + err = db.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(expectedInternalKey)) + require.NoError(t, err) - // Save - err := kv.Save(ctx, "section", key, bytes.NewReader(value)) - require.NoError(t, err) + // Verify the value is correct + valueBytes, err := item.ValueCopy(nil) + require.NoError(t, err) + require.Equal(t, value, string(valueBytes)) - // Get - obj, err := kv.Get(ctx, "section", key) - require.NoError(t, err) - assert.Equal(t, key, obj.Key) + return nil + }) + require.NoError(t, err) + }) - readValue, err := io.ReadAll(obj.Value) - require.NoError(t, err) - assert.Equal(t, value, readValue) + t.Run("sections are properly isolated", func(t *testing.T) { + section1 := "section1" + section2 := "section2" + key := "same-key" + value1 := "value-from-section1" + value2 := "value-from-section2" - // Delete - err = kv.Delete(ctx, "section", key) - require.NoError(t, err) + // Save same key in different sections + err := kv.Save(ctx, section1, key, strings.NewReader(value1)) + require.NoError(t, err) + err = kv.Save(ctx, section2, key, strings.NewReader(value2)) + require.NoError(t, err) - // Verify deleted - _, err = kv.Get(ctx, "section", key) - assert.Error(t, err) - }(i) + // Verify both keys exist in badger with different internal keys + err = db.View(func(txn *badger.Txn) error { + // Check section1 key + item1, err := txn.Get([]byte(section1 + "/" + key)) + require.NoError(t, err) + value1Bytes, err := item1.ValueCopy(nil) + require.NoError(t, err) + require.Equal(t, value1, string(value1Bytes)) + + // Check section2 key + item2, err := txn.Get([]byte(section2 + "/" + key)) + require.NoError(t, err) + value2Bytes, err := item2.ValueCopy(nil) + require.NoError(t, err) + require.Equal(t, value2, string(value2Bytes)) + + return nil + }) + require.NoError(t, err) + + // Verify KV interface returns correct values for each section + obj1, err := kv.Get(ctx, section1, key) + require.NoError(t, err) + val1, err := io.ReadAll(obj1.Value) + require.NoError(t, err) + require.Equal(t, value1, string(val1)) + err = obj1.Value.Close() + require.NoError(t, err) + + obj2, err := kv.Get(ctx, section2, key) + require.NoError(t, err) + val2, err := io.ReadAll(obj2.Value) + require.NoError(t, err) + require.Equal(t, value2, string(val2)) + err = obj2.Value.Close() + require.NoError(t, err) + }) + + t.Run("delete removes correct internal key", func(t *testing.T) { + section := "delete-section" + key := "delete-key" + value := "delete-value" + internalKey := section + "/" + key + + // Save and verify it exists + err := kv.Save(ctx, section, key, strings.NewReader(value)) + require.NoError(t, err) + + // Verify it exists in badger + err = db.View(func(txn *badger.Txn) error { + _, err := txn.Get([]byte(internalKey)) + return err + }) + require.NoError(t, err) + + // Delete through KV interface + err = kv.Delete(ctx, section, key) + require.NoError(t, err) + + // Verify it's gone from badger + err = db.View(func(txn *badger.Txn) error { + _, err := txn.Get([]byte(internalKey)) + return err + }) + require.Error(t, err) + require.Equal(t, badger.ErrKeyNotFound, err) + }) + + t.Run("keys iteration respects section boundaries", func(t *testing.T) { + section1 := "alpha" + section2 := "beta" + + // Add keys to both sections + keys1 := []string{"a1", "a2", "a3"} + keys2 := []string{"b1", "b2", "b3"} + + for _, k := range keys1 { + err := kv.Save(ctx, section1, k, strings.NewReader("value"+k)) + require.NoError(t, err) + } + for _, k := range keys2 { + err := kv.Save(ctx, section2, k, strings.NewReader("value"+k)) + require.NoError(t, err) } - // Wait for all goroutines to complete - for i := 0; i < numGoroutines; i++ { - <-done + // List keys from section1 only + var foundKeys1 []string + for k, err := range kv.Keys(ctx, section1, ListOptions{}) { + require.NoError(t, err) + foundKeys1 = append(foundKeys1, k) + } + require.Equal(t, keys1, foundKeys1) + + // List keys from section2 only + var foundKeys2 []string + for k, err := range kv.Keys(ctx, section2, ListOptions{}) { + require.NoError(t, err) + foundKeys2 = append(foundKeys2, k) + } + require.Equal(t, keys2, foundKeys2) + + // Verify raw badger contains all keys with proper prefixes + var allRawKeys []string + err := db.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false + iter := txn.NewIterator(opts) + defer iter.Close() + + for iter.Rewind(); iter.Valid(); iter.Next() { + item := iter.Item() + allRawKeys = append(allRawKeys, string(item.Key())) + } + return nil + }) + require.NoError(t, err) + + // Check that all expected internal keys exist + expectedInternalKeys := []string{ + "alpha/a1", "alpha/a2", "alpha/a3", + "beta/b1", "beta/b2", "beta/b3", + } + for _, expectedKey := range expectedInternalKeys { + require.Contains(t, allRawKeys, expectedKey, "Expected internal key %s should exist", expectedKey) } }) } diff --git a/pkg/storage/unified/resource/storage_backend.go b/pkg/storage/unified/resource/storage_backend.go new file mode 100644 index 00000000000..53a898e50b2 --- /dev/null +++ b/pkg/storage/unified/resource/storage_backend.go @@ -0,0 +1,798 @@ +package resource + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "math/rand/v2" + "net/http" + "sort" + "strings" + "time" + + "github.com/bwmarrin/snowflake" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + defaultListBufferSize = 100 +) + +// Unified storage backend based on KV storage. +type kvStorageBackend struct { + snowflake *snowflake.Node + kv KV + dataStore *dataStore + metaStore *metadataStore + eventStore *eventStore + notifier *notifier + builder DocumentBuilder + log logging.Logger +} + +var _ StorageBackend = &kvStorageBackend{} + +func NewKvStorageBackend(kv KV) *kvStorageBackend { + s, err := snowflake.NewNode(rand.Int64N(1024)) + if err != nil { + panic(err) + } + eventStore := newEventStore(kv) + return &kvStorageBackend{ + kv: kv, + dataStore: newDataStore(kv), + metaStore: newMetadataStore(kv), + eventStore: eventStore, + notifier: newNotifier(eventStore, notifierOptions{}), + snowflake: s, + builder: StandardDocumentBuilder(), // For now we use the standard document builder. + log: &logging.NoOpLogger{}, // Make this configurable + } +} + +// WriteEvent writes a resource event (create/update/delete) to the storage backend. +func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (int64, error) { + if err := event.Validate(); err != nil { + return 0, fmt.Errorf("invalid event: %w", err) + } + rv := k.snowflake.Generate().Int64() + + // Write data. + var action DataAction + switch event.Type { + case resourcepb.WatchEvent_ADDED: + action = DataActionCreated + // Check if resource already exists for create operations + _, err := k.metaStore.GetLatestResourceKey(ctx, MetaGetRequestKey{ + Namespace: event.Key.Namespace, + Group: event.Key.Group, + Resource: event.Key.Resource, + Name: event.Key.Name, + }) + if err == nil { + // Resource exists, return already exists error + return 0, ErrResourceAlreadyExists + } + if !errors.Is(err, ErrNotFound) { + // Some other error occurred + return 0, fmt.Errorf("failed to check if resource exists: %w", err) + } + case resourcepb.WatchEvent_MODIFIED: + action = DataActionUpdated + case resourcepb.WatchEvent_DELETED: + action = DataActionDeleted + default: + return 0, fmt.Errorf("invalid event type: %d", event.Type) + } + + // Build the search document + doc, err := k.builder.BuildDocument(ctx, event.Key, rv, event.Value) + if err != nil { + return 0, fmt.Errorf("failed to build document: %w", err) + } + + // Write the data + err = k.dataStore.Save(ctx, DataKey{ + Namespace: event.Key.Namespace, + Group: event.Key.Group, + Resource: event.Key.Resource, + Name: event.Key.Name, + ResourceVersion: rv, + Action: action, + }, bytes.NewReader(event.Value)) + if err != nil { + return 0, fmt.Errorf("failed to write data: %w", err) + } + + // Write metadata + err = k.metaStore.Save(ctx, MetaDataObj{ + Key: MetaDataKey{ + Namespace: event.Key.Namespace, + Group: event.Key.Group, + Resource: event.Key.Resource, + Name: event.Key.Name, + ResourceVersion: rv, + Action: action, + Folder: event.Object.GetFolder(), + }, + Value: MetaData{ + IndexableDocument: *doc, + }, + }) + if err != nil { + return 0, fmt.Errorf("failed to write metadata: %w", err) + } + + // Write event + err = k.eventStore.Save(ctx, Event{ + Namespace: event.Key.Namespace, + Group: event.Key.Group, + Resource: event.Key.Resource, + Name: event.Key.Name, + ResourceVersion: rv, + Action: action, + Folder: event.Object.GetFolder(), + PreviousRV: event.PreviousRV, + }) + if err != nil { + return 0, fmt.Errorf("failed to save event: %w", err) + } + return rv, nil +} + +func (k *kvStorageBackend) ReadResource(ctx context.Context, req *resourcepb.ReadRequest) *BackendReadResponse { + if req.Key == nil { + return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusBadRequest, Message: "missing key"}} + } + meta, err := k.metaStore.GetResourceKeyAtRevision(ctx, MetaGetRequestKey{ + Namespace: req.Key.Namespace, + Group: req.Key.Group, + Resource: req.Key.Resource, + Name: req.Key.Name, + }, req.ResourceVersion) + if errors.Is(err, ErrNotFound) { + return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusNotFound, Message: "not found"}} + } else if err != nil { + return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusInternalServerError, Message: err.Error()}} + } + data, err := k.dataStore.Get(ctx, DataKey{ + Namespace: req.Key.Namespace, + Group: req.Key.Group, + Resource: req.Key.Resource, + Name: req.Key.Name, + ResourceVersion: meta.ResourceVersion, + Action: meta.Action, + }) + if err != nil || data == nil { + return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusInternalServerError, Message: err.Error()}} + } + value, err := readAndClose(data) + if err != nil { + return &BackendReadResponse{Error: &resourcepb.ErrorResult{Code: http.StatusInternalServerError, Message: err.Error()}} + } + return &BackendReadResponse{ + Key: req.Key, + ResourceVersion: meta.ResourceVersion, + Value: value, + Folder: meta.Folder, + } +} + +// ListIterator returns an iterator for listing resources. +func (k *kvStorageBackend) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(ListIterator) error) (int64, error) { + if req.Options == nil || req.Options.Key == nil { + return 0, fmt.Errorf("missing options or key in ListRequest") + } + // Parse continue token if provided + offset := int64(0) + resourceVersion := req.ResourceVersion + if req.NextPageToken != "" { + token, err := GetContinueToken(req.NextPageToken) + if err != nil { + return 0, fmt.Errorf("invalid continue token: %w", err) + } + offset = token.StartOffset + resourceVersion = token.ResourceVersion + } + + // We set the listRV to the current time. + listRV := k.snowflake.Generate().Int64() + if resourceVersion > 0 { + listRV = resourceVersion + } + + // Fetch the latest objects + keys := make([]MetaDataKey, 0, min(defaultListBufferSize, req.Limit+1)) + idx := 0 + for metaKey, err := range k.metaStore.ListResourceKeysAtRevision(ctx, MetaListRequestKey{ + Namespace: req.Options.Key.Namespace, + Group: req.Options.Key.Group, + Resource: req.Options.Key.Resource, + Name: req.Options.Key.Name, + }, resourceVersion) { + if err != nil { + return 0, err + } + // Skip the first offset items. This is not efficient, but it's a simple way to implement it for now. + if idx < int(offset) { + idx++ + continue + } + keys = append(keys, metaKey) + // Only fetch the first limit items + 1 to get the next token. + if len(keys) >= int(req.Limit+1) { + break + } + } + iter := kvListIterator{ + keys: keys, + currentIndex: -1, + ctx: ctx, + listRV: listRV, + offset: offset, + limit: req.Limit + 1, // TODO: for now we need at least one more item. Fix the caller + dataStore: k.dataStore, + } + err := cb(&iter) + if err != nil { + return 0, err + } + + return listRV, nil +} + +// kvListIterator implements ListIterator for KV storage +type kvListIterator struct { + ctx context.Context + keys []MetaDataKey + currentIndex int + dataStore *dataStore + listRV int64 + offset int64 + limit int64 + + // current + rv int64 + err error + value []byte +} + +func (i *kvListIterator) Next() bool { + i.currentIndex++ + + if i.currentIndex >= len(i.keys) { + return false + } + + if int64(i.currentIndex) >= i.limit { + return false + } + + i.rv, i.err = i.keys[i.currentIndex].ResourceVersion, nil + + data, err := i.dataStore.Get(i.ctx, DataKey{ + Namespace: i.keys[i.currentIndex].Namespace, + Group: i.keys[i.currentIndex].Group, + Resource: i.keys[i.currentIndex].Resource, + Name: i.keys[i.currentIndex].Name, + ResourceVersion: i.keys[i.currentIndex].ResourceVersion, + Action: i.keys[i.currentIndex].Action, + }) + if err != nil { + i.err = err + return false + } + + i.value, i.err = readAndClose(data) + if i.err != nil { + return false + } + + // increment the offset + i.offset++ + + return true +} + +func (i *kvListIterator) Error() error { + return nil +} + +func (i *kvListIterator) ContinueToken() string { + return ContinueToken{ + StartOffset: i.offset, + ResourceVersion: i.listRV, + }.String() +} + +func (i *kvListIterator) ResourceVersion() int64 { + return i.rv +} + +func (i *kvListIterator) Namespace() string { + return i.keys[i.currentIndex].Namespace +} + +func (i *kvListIterator) Name() string { + return i.keys[i.currentIndex].Name +} + +func (i *kvListIterator) Folder() string { + return i.keys[i.currentIndex].Folder +} + +func (i *kvListIterator) Value() []byte { + return i.value +} + +func validateListHistoryRequest(req *resourcepb.ListRequest) error { + if req.Options == nil || req.Options.Key == nil { + return fmt.Errorf("missing options or key in ListRequest") + } + key := req.Options.Key + if key.Group == "" { + return fmt.Errorf("group is required") + } + if key.Resource == "" { + return fmt.Errorf("resource is required") + } + if key.Namespace == "" { + return fmt.Errorf("namespace is required") + } + if key.Name == "" { + return fmt.Errorf("name is required") + } + return nil +} + +// filterHistoryKeysByVersion filters history keys based on version match criteria +func filterHistoryKeysByVersion(historyKeys []DataKey, req *resourcepb.ListRequest) ([]DataKey, error) { + switch req.GetVersionMatchV2() { + case resourcepb.ResourceVersionMatchV2_Exact: + if req.ResourceVersion <= 0 { + return nil, fmt.Errorf("expecting an explicit resource version query when using Exact matching") + } + var exactKeys []DataKey + for _, key := range historyKeys { + if key.ResourceVersion == req.ResourceVersion { + exactKeys = append(exactKeys, key) + } + } + return exactKeys, nil + case resourcepb.ResourceVersionMatchV2_NotOlderThan: + if req.ResourceVersion > 0 { + var filteredKeys []DataKey + for _, key := range historyKeys { + if key.ResourceVersion >= req.ResourceVersion { + filteredKeys = append(filteredKeys, key) + } + } + return filteredKeys, nil + } + default: + if req.ResourceVersion > 0 { + var filteredKeys []DataKey + for _, key := range historyKeys { + if key.ResourceVersion <= req.ResourceVersion { + filteredKeys = append(filteredKeys, key) + } + } + return filteredKeys, nil + } + } + return historyKeys, nil +} + +// applyLiveHistoryFilter applies "live" history logic by ignoring events before the last delete +func applyLiveHistoryFilter(filteredKeys []DataKey, req *resourcepb.ListRequest) []DataKey { + useLatestDeletionAsMinRV := req.ResourceVersion == 0 && req.Source != resourcepb.ListRequest_TRASH && req.GetVersionMatchV2() != resourcepb.ResourceVersionMatchV2_Exact + if !useLatestDeletionAsMinRV { + return filteredKeys + } + + latestDeleteRV := int64(0) + for _, key := range filteredKeys { + if key.Action == DataActionDeleted && key.ResourceVersion > latestDeleteRV { + latestDeleteRV = key.ResourceVersion + } + } + if latestDeleteRV > 0 { + var liveKeys []DataKey + for _, key := range filteredKeys { + if key.ResourceVersion > latestDeleteRV { + liveKeys = append(liveKeys, key) + } + } + return liveKeys + } + return filteredKeys +} + +// sortByResourceVersion sorts the history keys based on the sortAscending flag +func sortByResourceVersion(filteredKeys []DataKey, sortAscending bool) { + if sortAscending { + sort.Slice(filteredKeys, func(i, j int) bool { + return filteredKeys[i].ResourceVersion < filteredKeys[j].ResourceVersion + }) + } else { + sort.Slice(filteredKeys, func(i, j int) bool { + return filteredKeys[i].ResourceVersion > filteredKeys[j].ResourceVersion + }) + } +} + +// applyPagination filters keys based on pagination parameters +func applyPagination(keys []DataKey, lastSeenRV int64, sortAscending bool) []DataKey { + if lastSeenRV == 0 { + return keys + } + + var pagedKeys []DataKey + for _, key := range keys { + if sortAscending && key.ResourceVersion > lastSeenRV { + pagedKeys = append(pagedKeys, key) + } else if !sortAscending && key.ResourceVersion < lastSeenRV { + pagedKeys = append(pagedKeys, key) + } + } + return pagedKeys +} + +// ListHistory is like ListIterator, but it returns the history of a resource. +func (k *kvStorageBackend) ListHistory(ctx context.Context, req *resourcepb.ListRequest, fn func(ListIterator) error) (int64, error) { + if err := validateListHistoryRequest(req); err != nil { + return 0, err + } + key := req.Options.Key + // Parse continue token if provided + lastSeenRV := int64(0) + sortAscending := req.GetVersionMatchV2() == resourcepb.ResourceVersionMatchV2_NotOlderThan + if req.NextPageToken != "" { + token, err := GetContinueToken(req.NextPageToken) + if err != nil { + return 0, fmt.Errorf("invalid continue token: %w", err) + } + lastSeenRV = token.ResourceVersion + sortAscending = token.SortAscending + } + + // Generate a new resource version for the list + listRV := k.snowflake.Generate().Int64() + + // Get all history entries by iterating through datastore keys + historyKeys := make([]DataKey, 0, min(defaultListBufferSize, req.Limit+1)) + + // Use datastore.Keys to get all data keys for this specific resource + for dataKey, err := range k.dataStore.Keys(ctx, ListRequestKey{ + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + Name: key.Name, + }) { + if err != nil { + return 0, err + } + historyKeys = append(historyKeys, dataKey) + } + + // Check if context has been cancelled + if ctx.Err() != nil { + return 0, ctx.Err() + } + + // Handle trash differently from regular history + if req.Source == resourcepb.ListRequest_TRASH { + return k.processTrashEntries(ctx, req, fn, historyKeys, lastSeenRV, sortAscending, listRV) + } + + // Apply filtering based on version match + filteredKeys, filterErr := filterHistoryKeysByVersion(historyKeys, req) + if filterErr != nil { + return 0, filterErr + } + + // Apply "live" history logic: ignore events before the last delete + filteredKeys = applyLiveHistoryFilter(filteredKeys, req) + + // Sort the entries if not already sorted correctly + sortByResourceVersion(filteredKeys, sortAscending) + + // Pagination: filter out items up to and including lastSeenRV + pagedKeys := applyPagination(filteredKeys, lastSeenRV, sortAscending) + + iter := kvHistoryIterator{ + keys: pagedKeys, + currentIndex: -1, + ctx: ctx, + listRV: listRV, + sortAscending: sortAscending, + dataStore: k.dataStore, + } + + err := fn(&iter) + if err != nil { + return 0, err + } + + return listRV, nil +} + +// processTrashEntries handles the special case of listing deleted items (trash) +func (k *kvStorageBackend) processTrashEntries(ctx context.Context, req *resourcepb.ListRequest, fn func(ListIterator) error, historyKeys []DataKey, lastSeenRV int64, sortAscending bool, listRV int64) (int64, error) { + // Filter to only deleted entries + var deletedKeys []DataKey + for _, key := range historyKeys { + if key.Action == DataActionDeleted { + deletedKeys = append(deletedKeys, key) + } + } + + // Check if the resource currently exists (is live) + // If it exists, don't return any trash entries + _, err := k.metaStore.GetLatestResourceKey(ctx, MetaGetRequestKey{ + Namespace: req.Options.Key.Namespace, + Group: req.Options.Key.Group, + Resource: req.Options.Key.Resource, + Name: req.Options.Key.Name, + }) + + var trashKeys []DataKey + if errors.Is(err, ErrNotFound) { + // Resource doesn't exist currently, so we can return the latest delete + // Find the latest delete event + var latestDelete *DataKey + for _, key := range deletedKeys { + if latestDelete == nil || key.ResourceVersion > latestDelete.ResourceVersion { + latestDelete = &key + } + } + if latestDelete != nil { + trashKeys = append(trashKeys, *latestDelete) + } + } + // If err != ErrNotFound, the resource exists, so no trash entries should be returned + + // Apply version filtering + filteredKeys, err := filterHistoryKeysByVersion(trashKeys, req) + if err != nil { + return 0, err + } + + // Sort the entries + sortByResourceVersion(filteredKeys, sortAscending) + + // Pagination: filter out items up to and including lastSeenRV + pagedKeys := applyPagination(filteredKeys, lastSeenRV, sortAscending) + + iter := kvHistoryIterator{ + keys: pagedKeys, + currentIndex: -1, + ctx: ctx, + listRV: listRV, + sortAscending: sortAscending, + dataStore: k.dataStore, + } + + err = fn(&iter) + if err != nil { + return 0, err + } + + return listRV, nil +} + +// kvHistoryIterator implements ListIterator for KV storage history +type kvHistoryIterator struct { + ctx context.Context + keys []DataKey + currentIndex int + listRV int64 + sortAscending bool + dataStore *dataStore + + // current + rv int64 + err error + value []byte + folder string +} + +func (i *kvHistoryIterator) Next() bool { + i.currentIndex++ + + if i.currentIndex >= len(i.keys) { + return false + } + + key := i.keys[i.currentIndex] + i.rv = key.ResourceVersion + + // Read the value from the ReadCloser + data, err := i.dataStore.Get(i.ctx, key) + if err != nil { + i.err = err + return false + } + if data == nil { + i.err = fmt.Errorf("data is nil") + return false + } + i.value, i.err = readAndClose(data) + if i.err != nil { + return false + } + + // Extract the folder from the meta data + partial := &metav1.PartialObjectMetadata{} + err = json.Unmarshal(i.value, partial) + if err != nil { + i.err = err + return false + } + + meta, err := utils.MetaAccessor(partial) + if err != nil { + i.err = err + return false + } + i.folder = meta.GetFolder() + i.err = nil + + return true +} + +func (i *kvHistoryIterator) Error() error { + return i.err +} + +func (i *kvHistoryIterator) ContinueToken() string { + if i.currentIndex < 0 || i.currentIndex >= len(i.keys) { + return "" + } + token := ContinueToken{ + StartOffset: i.rv, + ResourceVersion: i.keys[i.currentIndex].ResourceVersion, + SortAscending: i.sortAscending, + } + return token.String() +} + +func (i *kvHistoryIterator) ResourceVersion() int64 { + return i.rv +} + +func (i *kvHistoryIterator) Namespace() string { + if i.currentIndex >= 0 && i.currentIndex < len(i.keys) { + return i.keys[i.currentIndex].Namespace + } + return "" +} + +func (i *kvHistoryIterator) Name() string { + if i.currentIndex >= 0 && i.currentIndex < len(i.keys) { + return i.keys[i.currentIndex].Name + } + return "" +} + +func (i *kvHistoryIterator) Folder() string { + return i.folder +} + +func (i *kvHistoryIterator) Value() []byte { + return i.value +} + +// WatchWriteEvents returns a channel that receives write events. +func (k *kvStorageBackend) WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) { + // Create a channel to receive events + events := make(chan *WrittenEvent, 10000) // TODO: make this configurable + + notifierEvents := k.notifier.Watch(ctx, defaultWatchOptions()) + go func() { + for event := range notifierEvents { + // fetch the data + dataReader, err := k.dataStore.Get(ctx, DataKey{ + Namespace: event.Namespace, + Group: event.Group, + Resource: event.Resource, + Name: event.Name, + ResourceVersion: event.ResourceVersion, + Action: event.Action, + }) + if err != nil || dataReader == nil { + k.log.Error("failed to get data for event", "error", err) + continue + } + data, err := readAndClose(dataReader) + if err != nil { + k.log.Error("failed to read and close data for event", "error", err) + continue + } + var t resourcepb.WatchEvent_Type + switch event.Action { + case DataActionCreated: + t = resourcepb.WatchEvent_ADDED + case DataActionUpdated: + t = resourcepb.WatchEvent_MODIFIED + case DataActionDeleted: + t = resourcepb.WatchEvent_DELETED + } + + events <- &WrittenEvent{ + Key: &resourcepb.ResourceKey{ + Namespace: event.Namespace, + Group: event.Group, + Resource: event.Resource, + Name: event.Name, + }, + Type: t, + Folder: event.Folder, + Value: data, + ResourceVersion: event.ResourceVersion, + PreviousRV: event.PreviousRV, + Timestamp: event.ResourceVersion / time.Second.Nanoseconds(), // convert to seconds + } + } + close(events) + }() + return events, nil +} + +// GetResourceStats returns resource stats within the storage backend. +// TODO: this isn't very efficient, we should use a more efficient algorithm. +func (k *kvStorageBackend) GetResourceStats(ctx context.Context, namespace string, minCount int) ([]ResourceStats, error) { + stats := make([]ResourceStats, 0) + res := make(map[string]map[string]bool) + rvs := make(map[string]int64) + + // Use datastore.Keys to get all data keys for the namespace + for dataKey, err := range k.dataStore.Keys(ctx, ListRequestKey{Namespace: namespace}) { + if err != nil { + return nil, err + } + key := fmt.Sprintf("%s/%s/%s", dataKey.Namespace, dataKey.Group, dataKey.Resource) + if _, ok := res[key]; !ok { + res[key] = make(map[string]bool) + rvs[key] = 1 + } + res[key][dataKey.Name] = dataKey.Action != DataActionDeleted + rvs[key] = dataKey.ResourceVersion + } + + for key, names := range res { + parts := strings.Split(key, "/") + count := int64(0) + for _, exists := range names { + if exists { + count++ + } + } + if count <= int64(minCount) { + continue + } + stats = append(stats, ResourceStats{ + NamespacedResource: NamespacedResource{ + Namespace: parts[0], + Group: parts[1], + Resource: parts[2], + }, + Count: count, + ResourceVersion: rvs[key], + }) + } + return stats, nil +} + +// readAndClose reads all data from a ReadCloser and ensures it's closed, +// combining any errors from both operations. +func readAndClose(r io.ReadCloser) ([]byte, error) { + data, err := io.ReadAll(r) + return data, errors.Join(err, r.Close()) +} diff --git a/pkg/storage/unified/resource/storage_backend_test.go b/pkg/storage/unified/resource/storage_backend_test.go new file mode 100644 index 00000000000..899f7b2de25 --- /dev/null +++ b/pkg/storage/unified/resource/storage_backend_test.go @@ -0,0 +1,1007 @@ +package resource + +import ( + "context" + "fmt" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" +) + +func setupTestStorageBackend(t *testing.T) *kvStorageBackend { + kv := setupTestKV(t) + return NewKvStorageBackend(kv) +} + +func TestNewKvStorageBackend(t *testing.T) { + backend := setupTestStorageBackend(t) + + assert.NotNil(t, backend) + assert.NotNil(t, backend.kv) + assert.NotNil(t, backend.dataStore) + assert.NotNil(t, backend.metaStore) + assert.NotNil(t, backend.eventStore) + assert.NotNil(t, backend.notifier) + assert.NotNil(t, backend.snowflake) +} + +func TestKvStorageBackend_WriteEvent_Success(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + tests := []struct { + name string + eventType resourcepb.WatchEvent_Type + }{ + { + name: "write ADDED event", + eventType: resourcepb.WatchEvent_ADDED, + }, + { + name: "write MODIFIED event", + eventType: resourcepb.WatchEvent_MODIFIED, + }, + { + name: "write DELETED event", + eventType: resourcepb.WatchEvent_DELETED, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testObj, err := createTestObject() + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: tt.eventType, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 100, + } + + rv, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + assert.Greater(t, rv, int64(0), "resource version should be positive") + + // Verify data was written to dataStore + var expectedAction DataAction + switch tt.eventType { + case resourcepb.WatchEvent_ADDED: + expectedAction = DataActionCreated + case resourcepb.WatchEvent_MODIFIED: + expectedAction = DataActionUpdated + case resourcepb.WatchEvent_DELETED: + expectedAction = DataActionDeleted + default: + t.Fatalf("unexpected event type: %v", tt.eventType) + } + + dataKey := DataKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + ResourceVersion: rv, + Action: expectedAction, + } + + dataReader, err := backend.dataStore.Get(ctx, dataKey) + require.NoError(t, err) + dataValue, err := io.ReadAll(dataReader) + require.NoError(t, err) + require.NoError(t, dataReader.Close()) + assert.Equal(t, objectToJSONBytes(t, testObj), dataValue) + + // Verify metadata was written to metaStore + metaKey := MetaDataKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + ResourceVersion: rv, + Action: expectedAction, + Folder: "", + } + + m, err := backend.metaStore.Get(ctx, metaKey) + require.NoError(t, err) + require.NotNil(t, m) + require.Equal(t, "test-resource", m.Key.Name) + require.Equal(t, "default", m.Key.Namespace) + require.Equal(t, "apps", m.Key.Group) + require.Equal(t, "resources", m.Key.Resource) + + // Verify event was written to eventStore + eventKey := EventKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + ResourceVersion: rv, + } + + _, err = backend.eventStore.Get(ctx, eventKey) + require.NoError(t, err) + }) + } +} + +func TestKvStorageBackend_WriteEvent_ResourceAlreadyExists(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create a test resource first + testObj, err := createTestObject() + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + // First create should succeed + rv1, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + require.Greater(t, rv1, int64(0)) + + // Try to create the same resource again - should fail with ErrResourceAlreadyExists + writeEvent.PreviousRV = 0 // Reset previous RV to simulate a fresh create attempt + rv2, err := backend.WriteEvent(ctx, writeEvent) + require.Error(t, err) + require.Equal(t, int64(0), rv2) + require.ErrorIs(t, err, ErrResourceAlreadyExists) +} + +func TestKvStorageBackend_ReadResource_Success(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // First, write a resource to read + testObj, rv := createAndWriteTestObject(t, backend) + + // Now test reading the resource + readReq := &resourcepb.ReadRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + ResourceVersion: 0, // Read latest version + } + + response := backend.ReadResource(ctx, readReq) + require.Nil(t, response.Error, "ReadResource should succeed") + require.NotNil(t, response.Key, "Response should have a key") + require.Equal(t, "test-resource", response.Key.Name) + require.Equal(t, "default", response.Key.Namespace) + require.Equal(t, "apps", response.Key.Group) + require.Equal(t, "resources", response.Key.Resource) + require.Equal(t, rv, response.ResourceVersion) + require.Equal(t, objectToJSONBytes(t, testObj), response.Value) +} + +func TestKvStorageBackend_ReadResource_SpecificVersion(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create initial version + testObj, rv1 := createAndWriteTestObject(t, backend) + + // Update the resource + testObj.Object["spec"].(map[string]any)["value"] = "updated data" + rv2, err := writeObject(t, backend, testObj, resourcepb.WatchEvent_MODIFIED, rv1) + require.NoError(t, err) + + // Read the first version specifically + readReq := &resourcepb.ReadRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + ResourceVersion: rv1, + } + + response := backend.ReadResource(ctx, readReq) + require.Nil(t, response.Error, "ReadResource should succeed for specific version") + require.Equal(t, rv1, response.ResourceVersion) + + // Verify we got the original data, not the updated data + originalObj, err := createTestObject() + require.NoError(t, err) + require.Equal(t, objectToJSONBytes(t, originalObj), response.Value) + + // Read the latest version + readReq.ResourceVersion = 0 + response = backend.ReadResource(ctx, readReq) + require.Nil(t, response.Error, "ReadResource should succeed for latest version") + require.Equal(t, rv2, response.ResourceVersion) + require.Equal(t, objectToJSONBytes(t, testObj), response.Value) +} + +func TestKvStorageBackend_ReadResource_NotFound(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + readReq := &resourcepb.ReadRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "nonexistent-resource", + }, + ResourceVersion: 0, + } + + response := backend.ReadResource(ctx, readReq) + require.NotNil(t, response.Error, "ReadResource should return error for nonexistent resource") + require.Equal(t, int32(404), response.Error.Code) + require.Equal(t, "not found", response.Error.Message) + require.Nil(t, response.Key) + require.Equal(t, int64(0), response.ResourceVersion) + require.Nil(t, response.Value) +} + +func TestKvStorageBackend_ReadResource_MissingKey(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + readReq := &resourcepb.ReadRequest{ + Key: nil, // Missing key + ResourceVersion: 0, + } + + response := backend.ReadResource(ctx, readReq) + require.NotNil(t, response.Error, "ReadResource should return error for missing key") + require.Equal(t, int32(400), response.Error.Code) + require.Equal(t, "missing key", response.Error.Message) + require.Nil(t, response.Key) + require.Equal(t, int64(0), response.ResourceVersion) + require.Nil(t, response.Value) +} + +func TestKvStorageBackend_ReadResource_DeletedResource(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // First, create a resource + testObj, rv1 := createAndWriteTestObject(t, backend) + + // Delete the resource + _, err := writeObject(t, backend, testObj, resourcepb.WatchEvent_DELETED, rv1) + require.NoError(t, err) + + // Try to read the latest version (should be deleted and return not found) + readReq := &resourcepb.ReadRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + ResourceVersion: 0, + } + + response := backend.ReadResource(ctx, readReq) + require.NotNil(t, response.Error, "ReadResource should return not found for deleted resource") + require.Equal(t, int32(404), response.Error.Code) + require.Equal(t, "not found", response.Error.Message) + + // Try to read the original version (should still work) + readReq.ResourceVersion = rv1 + response = backend.ReadResource(ctx, readReq) + require.Nil(t, response.Error, "ReadResource should succeed for specific version before deletion") + require.Equal(t, rv1, response.ResourceVersion) + require.Equal(t, objectToJSONBytes(t, testObj), response.Value) +} + +func TestKvStorageBackend_ListIterator_Success(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create multiple test resources + resources := []struct { + name string + group string + value string + }{ + {"resource-1", "apps", "data-1"}, + {"resource-2", "apps", "data-2"}, + {"resource-3", "core", "data-3"}, + } + + for _, res := range resources { + testObj, err := createTestObjectWithName(res.name, res.group, res.value) + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: res.group, + Resource: "resources", + Name: res.name, + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + _, err = backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + } + + // Test listing all resources in "apps" group + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + }, + }, + Limit: 10, + } + + var collectedItems []struct { + name string + namespace string + resourceVersion int64 + value []byte + } + + rv, err := backend.ListIterator(ctx, listReq, func(iter ListIterator) error { + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + collectedItems = append(collectedItems, struct { + name string + namespace string + resourceVersion int64 + value []byte + }{ + name: iter.Name(), + namespace: iter.Namespace(), + resourceVersion: iter.ResourceVersion(), + value: iter.Value(), + }) + } + return iter.Error() + }) + + require.NoError(t, err) + require.Greater(t, rv, int64(0)) + require.Len(t, collectedItems, 2) // Only resources in "apps" group + + // Verify the items contain expected data + names := make([]string, len(collectedItems)) + for i, item := range collectedItems { + names[i] = item.name + require.Equal(t, "default", item.namespace) + require.Greater(t, item.resourceVersion, int64(0)) + require.NotEmpty(t, item.value) + } + require.Equal(t, []string{"resource-1", "resource-2"}, names) +} + +func TestKvStorageBackend_ListIterator_WithPagination(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create multiple test resources + for i := 1; i <= 5; i++ { + testObj, err := createTestObjectWithName(fmt.Sprintf("resource-%d", i), "apps", fmt.Sprintf("data-%d", i)) + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: fmt.Sprintf("resource-%d", i), + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + _, err = backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + } + + // First page with limit 2 + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + }, + }, + Limit: 2, + } + + var firstPageItems []string + var continueToken string + + rv, err := backend.ListIterator(ctx, listReq, func(iter ListIterator) error { + count := 0 + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + firstPageItems = append(firstPageItems, iter.Name()) + count++ + // Simulate pagination by getting continue token after limit items + if count >= int(listReq.Limit) { + continueToken = iter.ContinueToken() + break + } + } + return iter.Error() + }) + + require.NoError(t, err) + require.Greater(t, rv, int64(0)) + require.Len(t, firstPageItems, 2) + require.Equal(t, []string{"resource-1", "resource-2"}, firstPageItems) + require.NotEmpty(t, continueToken) + + // Second page using continue token + listReq.NextPageToken = continueToken + var secondPageItems []string + var continueToken2 string + + _, err = backend.ListIterator(ctx, listReq, func(iter ListIterator) error { + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + secondPageItems = append(secondPageItems, iter.Name()) + } + // Capture continue token for potential third page + continueToken2 = iter.ContinueToken() + return iter.Error() + }) + // TODO: fix the ListIterator to respect the limit. This require a change to the resource server. + require.NoError(t, err) + require.Equal(t, 3, len(secondPageItems)) + require.Equal(t, []string{"resource-3", "resource-4", "resource-5"}, secondPageItems) + require.NotEmpty(t, continueToken2) +} +func TestKvStorageBackend_ListIterator_EmptyResult(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "nonexistent", + Group: "apps", + Resource: "resources", + }, + }, + Limit: 10, + } + + var collectedItems []string + rv, err := backend.ListIterator(ctx, listReq, func(iter ListIterator) error { + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + collectedItems = append(collectedItems, iter.Name()) + } + return iter.Error() + }) + + require.NoError(t, err) + require.Greater(t, rv, int64(0)) + require.Empty(t, collectedItems) +} + +func TestKvStorageBackend_ListIterator_MissingOptions(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + tests := []struct { + name string + request *resourcepb.ListRequest + }{ + { + name: "nil options", + request: &resourcepb.ListRequest{ + Options: nil, + Limit: 10, + }, + }, + { + name: "nil key", + request: &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: nil, + }, + Limit: 10, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := backend.ListIterator(ctx, tt.request, func(iter ListIterator) error { + return nil + }) + require.Error(t, err) + require.Contains(t, err.Error(), "missing options or key") + }) + } +} + +func TestKvStorageBackend_ListIterator_InvalidContinueToken(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + }, + }, + Limit: 10, + NextPageToken: "invalid-token", + } + + _, err := backend.ListIterator(ctx, listReq, func(iter ListIterator) error { + return nil + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid continue token") +} + +func TestKvStorageBackend_ListIterator_SpecificResourceVersion(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create a resource + testObj, err := createTestObjectWithName("test-resource", "apps", "initial-data") + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + rv1, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // Update the resource + testObj.Object["spec"].(map[string]any)["value"] = "updated-data" + writeEvent.Type = resourcepb.WatchEvent_MODIFIED + writeEvent.Value = objectToJSONBytes(t, testObj) + writeEvent.PreviousRV = rv1 + + _, err = backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // List at specific resource version + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + }, + }, + ResourceVersion: rv1, + Limit: 10, + } + + var collectedItems [][]byte + rv, err := backend.ListIterator(ctx, listReq, func(iter ListIterator) error { + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + collectedItems = append(collectedItems, iter.Value()) + } + return iter.Error() + }) + + require.NoError(t, err) + require.Equal(t, rv1, rv) + require.Len(t, collectedItems, 1) + + // Verify we got the original data, not the updated data + originalObj, err := createTestObjectWithName("test-resource", "apps", "initial-data") + require.NoError(t, err) + require.Equal(t, objectToJSONBytes(t, originalObj), collectedItems[0]) +} + +func TestKvStorageBackend_ListHistory_Success(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create initial resource + testObj, err := createTestObjectWithName("test-resource", "apps", "initial-data") + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + rv1, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // Update the resource + testObj.Object["spec"].(map[string]any)["value"] = "updated-data" + writeEvent.Type = resourcepb.WatchEvent_MODIFIED + writeEvent.Value = objectToJSONBytes(t, testObj) + writeEvent.PreviousRV = rv1 + + rv2, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // Update again + testObj.Object["spec"].(map[string]any)["value"] = "final-data" + writeEvent.Value = objectToJSONBytes(t, testObj) + writeEvent.PreviousRV = rv2 + + rv3, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // List the history + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + }, + Source: resourcepb.ListRequest_HISTORY, + Limit: 10, + } + + var historyItems []struct { + resourceVersion int64 + value []byte + } + + rv, err := backend.ListHistory(ctx, listReq, func(iter ListIterator) error { + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + historyItems = append(historyItems, struct { + resourceVersion int64 + value []byte + }{ + resourceVersion: iter.ResourceVersion(), + value: iter.Value(), + }) + } + return iter.Error() + }) + + require.NoError(t, err) + require.Greater(t, rv, int64(0)) + require.Len(t, historyItems, 3) // Should have all 3 versions + + // Verify the history is sorted (newest first by default) + require.Equal(t, rv3, historyItems[0].resourceVersion) + require.Equal(t, rv2, historyItems[1].resourceVersion) + require.Equal(t, rv1, historyItems[2].resourceVersion) + + // Verify the content matches expectations for all versions + finalObj, err := createTestObjectWithName("test-resource", "apps", "final-data") + require.NoError(t, err) + require.Equal(t, objectToJSONBytes(t, finalObj), historyItems[0].value) + + updatedObj, err := createTestObjectWithName("test-resource", "apps", "updated-data") + require.NoError(t, err) + require.Equal(t, objectToJSONBytes(t, updatedObj), historyItems[1].value) + + initialObj, err := createTestObjectWithName("test-resource", "apps", "initial-data") + require.NoError(t, err) + require.Equal(t, objectToJSONBytes(t, initialObj), historyItems[2].value) +} + +func TestKvStorageBackend_ListTrash_Success(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create a resource + testObj, err := createTestObjectWithName("test-resource", "apps", "test-data") + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + rv1, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // Delete the resource + writeEvent.Type = resourcepb.WatchEvent_DELETED + writeEvent.PreviousRV = rv1 + + rv2, err := backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + + // List the trash (deleted items) + listReq := &resourcepb.ListRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: "default", + Group: "apps", + Resource: "resources", + Name: "test-resource", + }, + }, + Source: resourcepb.ListRequest_TRASH, + Limit: 10, + } + + var trashItems []struct { + name string + resourceVersion int64 + value []byte + } + + rv, err := backend.ListHistory(ctx, listReq, func(iter ListIterator) error { + for iter.Next() { + if err := iter.Error(); err != nil { + return err + } + trashItems = append(trashItems, struct { + name string + resourceVersion int64 + value []byte + }{ + name: iter.Name(), + resourceVersion: iter.ResourceVersion(), + value: iter.Value(), + }) + } + return iter.Error() + }) + + require.NoError(t, err) + require.Greater(t, rv, int64(0)) + require.Len(t, trashItems, 1) // Should have the deleted item + + // Verify the trash item + require.Equal(t, "test-resource", trashItems[0].name) + require.Equal(t, rv2, trashItems[0].resourceVersion) + require.Equal(t, objectToJSONBytes(t, testObj), trashItems[0].value) +} + +func TestKvStorageBackend_GetResourceStats_Success(t *testing.T) { + backend := setupTestStorageBackend(t) + ctx := context.Background() + + // Create resources in different groups and namespaces + resources := []struct { + namespace string + group string + resource string + name string + }{ + {"default", "apps", "resources", "app1"}, + {"default", "apps", "resources", "app2"}, + {"default", "core", "services", "svc1"}, + {"kube-system", "apps", "resources", "system-app"}, + {"kube-system", "core", "configmaps", "config1"}, + } + + for _, res := range resources { + testObj, err := createTestObjectWithName(res.name, res.group, "test-data") + require.NoError(t, err) + + metaAccessor, err := utils.MetaAccessor(testObj) + require.NoError(t, err) + + writeEvent := WriteEvent{ + Type: resourcepb.WatchEvent_ADDED, + Key: &resourcepb.ResourceKey{ + Namespace: res.namespace, + Group: res.group, + Resource: res.resource, + Name: res.name, + }, + Value: objectToJSONBytes(t, testObj), + Object: metaAccessor, + PreviousRV: 0, + } + + _, err = backend.WriteEvent(ctx, writeEvent) + require.NoError(t, err) + } + + // Get stats for default namespace + stats, err := backend.GetResourceStats(ctx, "default", 0) + require.NoError(t, err) + require.Len(t, stats, 2) // Should have stats for 2 resource types in default namespace + + // Verify the stats contain expected resource types + resourceTypes := make(map[string]int64) + for _, stat := range stats { + key := fmt.Sprintf("%s/%s/%s", stat.Namespace, stat.Group, stat.Resource) + resourceTypes[key] = stat.Count + require.Greater(t, stat.ResourceVersion, int64(0)) + } + + require.Equal(t, int64(2), resourceTypes["default/apps/resources"]) + require.Equal(t, int64(1), resourceTypes["default/core/services"]) + + // Get stats for all namespaces (empty string) + allStats, err := backend.GetResourceStats(ctx, "", 0) + require.NoError(t, err) + require.Len(t, allStats, 4) // Should have stats for all 4 resource types across namespaces + + // Get stats with minCount filter + filteredStats, err := backend.GetResourceStats(ctx, "", 1) + require.NoError(t, err) + require.Len(t, filteredStats, 1) // Only resources in default namespace has count > 1 + + require.Equal(t, "default", filteredStats[0].Namespace) + require.Equal(t, "apps", filteredStats[0].Group) + require.Equal(t, "resources", filteredStats[0].Resource) + require.Equal(t, int64(2), filteredStats[0].Count) +} + +// createTestObject creates a test unstructured object with standard values +func createTestObject() (*unstructured.Unstructured, error) { + return createTestObjectWithName("test-resource", "apps", "test data") +} + +// objectToJSONBytes converts an unstructured object to JSON bytes +func objectToJSONBytes(t *testing.T, obj *unstructured.Unstructured) []byte { + jsonBytes, err := obj.MarshalJSON() + require.NoError(t, err) + return jsonBytes +} + +// createTestObjectWithName creates a test unstructured object with specific name, group and value +func createTestObjectWithName(name, group, value string) (*unstructured.Unstructured, error) { + u := &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": group + "/v1", + "kind": "resource", + "metadata": map[string]any{ + "name": name, + "namespace": "default", + }, + "spec": map[string]any{ + "value": value, + }, + }, + } + return u, nil +} + +// writeObject writes an unstructured object to the backend using the provided event type and previous resource version +func writeObject(t *testing.T, backend *kvStorageBackend, obj *unstructured.Unstructured, eventType resourcepb.WatchEvent_Type, previousRV int64) (int64, error) { + metaAccessor, err := utils.MetaAccessor(obj) + require.NoError(t, err) + + // Extract resource information from the object + namespace := metaAccessor.GetNamespace() + if namespace == "" { + namespace = "default" + } + + // Extract group from apiVersion (e.g., "apps/v1" -> "apps") + apiVersion := obj.GetAPIVersion() + group := "" + if parts := strings.Split(apiVersion, "/"); len(parts) > 1 { + group = parts[0] + } + + // Use standard resource type for tests + resource := "resources" + if group == "core" { + resource = "services" + } + + writeEvent := WriteEvent{ + Type: eventType, + Key: &resourcepb.ResourceKey{ + Namespace: namespace, + Group: group, + Resource: resource, + Name: metaAccessor.GetName(), + }, + Value: objectToJSONBytes(t, obj), + Object: metaAccessor, + PreviousRV: previousRV, + } + + return backend.WriteEvent(context.Background(), writeEvent) +} + +// createAndWriteTestObject creates a basic test object and writes it to the backend +func createAndWriteTestObject(t *testing.T, backend *kvStorageBackend) (*unstructured.Unstructured, int64) { + testObj, err := createTestObject() + require.NoError(t, err) + + rv, err := writeObject(t, backend, testObj, resourcepb.WatchEvent_ADDED, 0) + require.NoError(t, err) + + return testObj, rv +} diff --git a/pkg/storage/unified/sql/db/dbimpl/db_engine.go b/pkg/storage/unified/sql/db/dbimpl/db_engine.go index ea638c2a53f..ccc994ec715 100644 --- a/pkg/storage/unified/sql/db/dbimpl/db_engine.go +++ b/pkg/storage/unified/sql/db/dbimpl/db_engine.go @@ -38,6 +38,12 @@ func getEngine(cfg *setting.Cfg) (*xorm.Engine, error) { if err != nil { return nil, fmt.Errorf("open database: %w", err) } + + engine.SetMaxOpenConns(dbSection.Key("max_open_conn").MustInt(0)) + engine.SetMaxIdleConns(dbSection.Key("max_idle_conn").MustInt(4)) + maxLifetime := time.Duration(dbSection.Key("conn_max_lifetime").MustInt(14400)) * time.Second + engine.SetConnMaxLifetime(maxLifetime) + return engine, nil default: return nil, fmt.Errorf("unsupported database type: %s", dbType) diff --git a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go index 98bba8aed1a..231e9525a6c 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go @@ -78,9 +78,10 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace. getter := newConfGetter(cfg.SectionWithEnvOverrides("resource_api"), "db_") fallbackGetter := newConfGetter(cfg.SectionWithEnvOverrides("database"), "") + logger := log.New("entity-db") p = &resourceDBProvider{ cfg: cfg, - log: log.New("entity-db"), + log: logger, logQueries: getter.Bool("log_queries"), migrateFunc: migrations.MigrateResourceStore, tracer: tracer, @@ -91,11 +92,13 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace. switch { // Deprecated: First try with the config in the "resource_api" section, which is specific to Unified Storage case dbType == dbTypePostgres: + logger.Info("Using resource_api section", "db_type", dbType) p.registerMetrics = true p.engine, err = getEnginePostgres(getter) return p, err case dbType == dbTypeMySQL: + logger.Info("Using resource_api section", "db_type", dbType) p.registerMetrics = true p.engine, err = getEngineMySQL(getter) return p, err @@ -105,6 +108,7 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace. // If we have an empty Resource API db config, try with the core Grafana database config case grafanaDBType != "": + logger.Info("Using database section", "db_type", grafanaDBType) p.registerMetrics = true p.engine, err = getEngine(cfg) return p, err @@ -121,6 +125,19 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace. } func (p *resourceDBProvider) init(ctx context.Context) (db.DB, error) { + p.log.Info("Initializing Resource DB", + "db_type", + p.engine.Dialect().DriverName(), + "open_conn", + p.engine.DB().DB.Stats().OpenConnections, + "in_use_conn", + p.engine.DB().DB.Stats().InUse, + "idle_conn", + p.engine.DB().DB.Stats().Idle, + "max_open_conn", + p.engine.DB().DB.Stats().MaxOpenConnections, + ) + if p.registerMetrics { err := prometheus.Register(sqlstats.NewStatsCollector("unified_storage", p.engine.DB().DB)) if err != nil { diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go new file mode 100644 index 00000000000..da91a8647d2 --- /dev/null +++ b/pkg/storage/unified/testing/kv.go @@ -0,0 +1,490 @@ +package test + +import ( + "bytes" + "context" + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/util/testutil" +) + +// Test names for the KV test suite +const ( + TestKVGet = "get operations" + TestKVSave = "save operations" + TestKVDelete = "delete operations" + TestKVKeys = "keys listing" + TestKVKeysWithLimits = "keys with limits and ranges" + TestKVKeysWithSort = "keys with sorting" + TestKVConcurrent = "concurrent operations" + TestKVUnixTimestamp = "unix timestamp" +) + +// NewKVFunc is a function that creates a new KV instance for testing +type NewKVFunc func(ctx context.Context) resource.KV + +// KVTestOptions configures which tests to run +type KVTestOptions struct { + NSPrefix string // namespace prefix for isolation +} + +// GenerateRandomKVPrefix creates a random namespace prefix for test isolation +func GenerateRandomKVPrefix() string { + return fmt.Sprintf("kvtest-%d", time.Now().UnixNano()) +} + +// RunKVTest runs the KV test suite +func RunKVTest(t *testing.T, newKV NewKVFunc, opts *KVTestOptions) { + if testing.Short() { + t.Skip("skipping integration test") + } + + if opts == nil { + opts = &KVTestOptions{} + } + + if opts.NSPrefix == "" { + opts.NSPrefix = GenerateRandomKVPrefix() + } + + t.Logf("Running KV tests with namespace prefix: %s", opts.NSPrefix) + + cases := []struct { + name string + fn func(*testing.T, resource.KV, string) + }{ + {TestKVGet, runTestKVGet}, + {TestKVSave, runTestKVSave}, + {TestKVDelete, runTestKVDelete}, + {TestKVKeys, runTestKVKeys}, + {TestKVKeysWithLimits, runTestKVKeysWithLimits}, + {TestKVKeysWithSort, runTestKVKeysWithSort}, + {TestKVConcurrent, runTestKVConcurrent}, + {TestKVUnixTimestamp, runTestKVUnixTimestamp}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tc.fn(t, newKV(context.Background()), opts.NSPrefix) + }) + } +} + +func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-get" + + t.Run("get existing key", func(t *testing.T) { + // First save a key + testValue := "test value for get" + err := kv.Save(ctx, section, "existing-key", strings.NewReader(testValue)) + require.NoError(t, err) + + // Now get it + obj, err := kv.Get(ctx, section, "existing-key") + require.NoError(t, err) + assert.Equal(t, "existing-key", obj.Key) + + // Read the value + value, err := io.ReadAll(obj.Value) + require.NoError(t, err) + assert.Equal(t, testValue, string(value)) + + // Close the value reader + err = obj.Value.Close() + require.NoError(t, err) + }) + + t.Run("get non-existent key", func(t *testing.T) { + _, err := kv.Get(ctx, section, "non-existent-key") + assert.Error(t, err) + assert.Equal(t, resource.ErrNotFound, err) + }) + + t.Run("get with empty section", func(t *testing.T) { + _, err := kv.Get(ctx, "", "some-key") + assert.Error(t, err) + assert.Contains(t, err.Error(), "section is required") + }) +} + +func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-save" + + t.Run("save new key", func(t *testing.T) { + testValue := "new test value" + err := kv.Save(ctx, section, "new-key", strings.NewReader(testValue)) + require.NoError(t, err) + + // Verify it was saved + obj, err := kv.Get(ctx, section, "new-key") + require.NoError(t, err) + assert.Equal(t, "new-key", obj.Key) + + value, err := io.ReadAll(obj.Value) + require.NoError(t, err) + assert.Equal(t, testValue, string(value)) + err = obj.Value.Close() + require.NoError(t, err) + }) + + t.Run("save overwrite existing key", func(t *testing.T) { + // First save + err := kv.Save(ctx, section, "overwrite-key", strings.NewReader("old value")) + require.NoError(t, err) + + // Overwrite + newValue := "new value" + err = kv.Save(ctx, section, "overwrite-key", strings.NewReader(newValue)) + require.NoError(t, err) + + // Verify it was updated + obj, err := kv.Get(ctx, section, "overwrite-key") + require.NoError(t, err) + + value, err := io.ReadAll(obj.Value) + require.NoError(t, err) + assert.Equal(t, newValue, string(value)) + err = obj.Value.Close() + require.NoError(t, err) + }) + + t.Run("save with empty section", func(t *testing.T) { + err := kv.Save(ctx, "", "some-key", strings.NewReader("some value")) + assert.Error(t, err) + assert.Contains(t, err.Error(), "section is required") + }) + + t.Run("save binary data", func(t *testing.T) { + binaryData := []byte{0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD} + err := kv.Save(ctx, section, "binary-key", bytes.NewReader(binaryData)) + require.NoError(t, err) + + // Verify binary data + obj, err := kv.Get(ctx, section, "binary-key") + require.NoError(t, err) + + value, err := io.ReadAll(obj.Value) + require.NoError(t, err) + assert.Equal(t, binaryData, value) + err = obj.Value.Close() + require.NoError(t, err) + }) +} + +func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-delete" + + t.Run("delete existing key", func(t *testing.T) { + // First create a key + err := kv.Save(ctx, section, "delete-key", strings.NewReader("delete me")) + require.NoError(t, err) + + // Verify it exists + _, err = kv.Get(ctx, section, "delete-key") + require.NoError(t, err) + + // Delete it + err = kv.Delete(ctx, section, "delete-key") + require.NoError(t, err) + + // Verify it's gone + _, err = kv.Get(ctx, section, "delete-key") + assert.Error(t, err) + assert.Equal(t, resource.ErrNotFound, err) + }) + + t.Run("delete non-existent key", func(t *testing.T) { + err := kv.Delete(ctx, section, "non-existent-delete-key") + assert.Error(t, err) + assert.Equal(t, resource.ErrNotFound, err) + }) + + t.Run("delete with empty section", func(t *testing.T) { + err := kv.Delete(ctx, "", "some-key") + assert.Error(t, err) + assert.Contains(t, err.Error(), "section is required") + }) +} + +func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-keys" + + // Setup test data + testKeys := []string{"a1", "a2", "b1", "b2", "c1"} + for _, key := range testKeys { + err := kv.Save(ctx, section, key, strings.NewReader("value"+key)) + require.NoError(t, err) + } + + t.Run("list all keys", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{}) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, testKeys, keys) + }) + + t.Run("list keys with empty section", func(t *testing.T) { + var keys []string + var errors []error + for k, err := range kv.Keys(ctx, "", resource.ListOptions{}) { + if err != nil { + errors = append(errors, err) + break + } + keys = append(keys, k) + } + assert.Len(t, errors, 1) + assert.Contains(t, errors[0].Error(), "section is required") + assert.Empty(t, keys) + }) +} + +func runTestKVKeysWithLimits(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-keys-limits" + + // Setup test data + testKeys := []string{"a1", "a2", "b1", "b2", "c1", "c2", "d1", "d2"} + for _, key := range testKeys { + err := kv.Save(ctx, section, key, strings.NewReader("value"+key)) + require.NoError(t, err) + } + + t.Run("keys with limit", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{Limit: 3}) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"a1", "a2", "b1"}, keys) + }) + + t.Run("keys with range", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{StartKey: "b", EndKey: "d"}) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"b1", "b2", "c1", "c2"}, keys) + }) + + t.Run("keys with prefix", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{ + StartKey: "c", + EndKey: resource.PrefixRangeEnd("c"), + }) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"c1", "c2"}, keys) + }) + + t.Run("keys with limit and range", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{ + StartKey: "a", + EndKey: "c", + Limit: 2, + }) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"a1", "a2"}, keys) + }) +} + +func runTestKVKeysWithSort(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-keys-sort" + + // Setup test data + testKeys := []string{"a1", "a2", "b1", "b2", "c1"} + for _, key := range testKeys { + err := kv.Save(ctx, section, key, strings.NewReader("value"+key)) + require.NoError(t, err) + } + + t.Run("keys in ascending order (default)", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{Sort: resource.SortOrderAsc}) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"a1", "a2", "b1", "b2", "c1"}, keys) + }) + + t.Run("keys in descending order", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{Sort: resource.SortOrderDesc}) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"c1", "b2", "b1", "a2", "a1"}, keys) + }) + + t.Run("keys descending with prefix", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{ + StartKey: "a", + EndKey: resource.PrefixRangeEnd("a"), + Sort: resource.SortOrderDesc, + }) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"a2", "a1"}, keys) + }) + + t.Run("keys descending with limit", func(t *testing.T) { + var keys []string + for k, err := range kv.Keys(ctx, section, resource.ListOptions{ + Sort: resource.SortOrderDesc, + Limit: 3, + }) { + require.NoError(t, err) + keys = append(keys, k) + } + assert.Equal(t, []string{"c1", "b2", "b1"}, keys) + }) +} + +func runTestKVConcurrent(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(60*time.Second)) + section := nsPrefix + "-concurrent" + + t.Run("concurrent save and get operations", func(t *testing.T) { + const numGoroutines = 10 + const numOperations = 20 + + done := make(chan error, numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(goroutineID int) { + var err error + defer func() { done <- err }() + + for j := 0; j < numOperations; j++ { + key := fmt.Sprintf("concurrent-key-%d-%d", goroutineID, j) + value := fmt.Sprintf("concurrent-value-%d-%d", goroutineID, j) + + // Save + err = kv.Save(ctx, section, key, strings.NewReader(value)) + if err != nil { + return + } + + // Get immediately + obj, err := kv.Get(ctx, section, key) + if err != nil { + return + } + + readValue, err := io.ReadAll(obj.Value) + require.NoError(t, err) + err = obj.Value.Close() + require.NoError(t, err) + assert.Equal(t, value, string(readValue)) + } + }(i) + } + + // Wait for all goroutines to complete + for i := 0; i < numGoroutines; i++ { + err := <-done + require.NoError(t, err) + } + }) + + t.Run("concurrent save, delete, and list operations", func(t *testing.T) { + const numGoroutines = 5 + done := make(chan error, numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(goroutineID int) { + var err error + defer func() { done <- err }() + + key := fmt.Sprintf("concurrent-ops-key-%d", goroutineID) + value := fmt.Sprintf("concurrent-ops-value-%d", goroutineID) + + // Save + err = kv.Save(ctx, section, key, strings.NewReader(value)) + if err != nil { + return + } + + // List to verify it exists + found := false + for k, err := range kv.Keys(ctx, section, resource.ListOptions{}) { + if err != nil { + return + } + if k == key { + found = true + break + } + } + if !found { + err = fmt.Errorf("key %s not found in list", key) + return + } + + // Delete + err = kv.Delete(ctx, section, key) + if err != nil { + return + } + + // Verify it's deleted + _, err = kv.Get(ctx, section, key) + require.ErrorIs(t, resource.ErrNotFound, err) + err = nil // Expected error, so clear it + }(i) + } + + // Wait for all goroutines to complete + for i := 0; i < numGoroutines; i++ { + err := <-done + require.NoError(t, err) + } + }) +} + +func runTestKVUnixTimestamp(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + + t.Run("unix timestamp returns reasonable value", func(t *testing.T) { + timestamp, err := kv.UnixTimestamp(ctx) + require.NoError(t, err) + + now := time.Now().Unix() + // Allow for some time difference (up to 5 seconds) + assert.InDelta(t, now, timestamp, 5) + }) + + t.Run("unix timestamp is consistent", func(t *testing.T) { + timestamp1, err := kv.UnixTimestamp(ctx) + require.NoError(t, err) + + timestamp2, err := kv.UnixTimestamp(ctx) + require.NoError(t, err) + + // Should be very close (within 1 second) + require.InDelta(t, timestamp1, timestamp2, 1) + }) +} diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go new file mode 100644 index 00000000000..1e9b1a16c45 --- /dev/null +++ b/pkg/storage/unified/testing/kv_test.go @@ -0,0 +1,28 @@ +package test + +import ( + "context" + "testing" + + badger "github.com/dgraph-io/badger/v4" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +func TestBadgerKV(t *testing.T) { + RunKVTest(t, func(ctx context.Context) resource.KV { + opts := badger.DefaultOptions("").WithInMemory(true).WithLogger(nil) + db, err := badger.Open(opts) + require.NoError(t, err) + + t.Cleanup(func() { + err := db.Close() + require.NoError(t, err) + }) + + return resource.NewBadgerKV(db) + }, &KVTestOptions{ + NSPrefix: "badger-kv-test", + }) +} diff --git a/pkg/storage/unified/testing/storage_backend.go b/pkg/storage/unified/testing/storage_backend.go index 45e756436fc..2f369c6d233 100644 --- a/pkg/storage/unified/testing/storage_backend.go +++ b/pkg/storage/unified/testing/storage_backend.go @@ -55,10 +55,6 @@ func GenerateRandomNSPrefix() string { // RunStorageBackendTest runs the storage backend test suite func RunStorageBackendTest(t *testing.T, newBackend NewBackendFunc, opts *TestOptions) { - if testing.Short() { - t.Skip("skipping integration test") - } - if opts == nil { opts = &TestOptions{} } @@ -228,37 +224,37 @@ func runTestIntegrationBackendGetResourceStats(t *testing.T, backend resource.St } // Create resources across different namespaces/groups _, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, - WithNamespace(nsPrefix+"-ns1"), + WithNamespace(nsPrefix+"-stats-ns1"), WithGroup("group"), WithResource("resource1")) require.NoError(t, err) _, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, - WithNamespace(nsPrefix+"-ns1"), + WithNamespace(nsPrefix+"-stats-ns1"), WithGroup("group"), WithResource("resource1")) require.NoError(t, err) _, err = writeEvent(ctx, backend, "item3", resourcepb.WatchEvent_ADDED, - WithNamespace(nsPrefix+"-ns1"), + WithNamespace(nsPrefix+"-stats-ns1"), WithGroup("group"), WithResource("resource2")) require.NoError(t, err) _, err = writeEvent(ctx, backend, "item4", resourcepb.WatchEvent_ADDED, - WithNamespace(nsPrefix+"-ns2"), + WithNamespace(nsPrefix+"-stats-ns2"), WithGroup("group"), WithResource("resource1")) require.NoError(t, err) _, err = writeEvent(ctx, backend, "item5", resourcepb.WatchEvent_ADDED, - WithNamespace(nsPrefix+"-ns2"), + WithNamespace(nsPrefix+"-stats-ns2"), WithGroup("group"), WithResource("resource1")) require.NoError(t, err) t.Run("Get stats for ns1", func(t *testing.T) { - stats, err := backend.GetResourceStats(ctx, nsPrefix+"-ns1", 0) + stats, err := backend.GetResourceStats(ctx, nsPrefix+"-stats-ns1", 0) require.NoError(t, err) require.Len(t, stats, 2) @@ -266,14 +262,14 @@ func runTestIntegrationBackendGetResourceStats(t *testing.T, backend resource.St slices.SortFunc(stats, sortFunc) // Check first resource stats - require.Equal(t, nsPrefix+"-ns1", stats[0].Namespace) + require.Equal(t, nsPrefix+"-stats-ns1", stats[0].Namespace) require.Equal(t, "group", stats[0].Group) require.Equal(t, "resource1", stats[0].Resource) require.Equal(t, int64(2), stats[0].Count) require.Greater(t, stats[0].ResourceVersion, int64(0)) // Check second resource stats - require.Equal(t, nsPrefix+"-ns1", stats[1].Namespace) + require.Equal(t, nsPrefix+"-stats-ns1", stats[1].Namespace) require.Equal(t, "group", stats[1].Group) require.Equal(t, "resource2", stats[1].Resource) require.Equal(t, int64(1), stats[1].Count) @@ -281,11 +277,11 @@ func runTestIntegrationBackendGetResourceStats(t *testing.T, backend resource.St }) t.Run("Get stats for ns2", func(t *testing.T) { - stats, err := backend.GetResourceStats(ctx, nsPrefix+"-ns2", 0) + stats, err := backend.GetResourceStats(ctx, nsPrefix+"-stats-ns2", 0) require.NoError(t, err) require.Len(t, stats, 1) - require.Equal(t, nsPrefix+"-ns2", stats[0].Namespace) + require.Equal(t, nsPrefix+"-stats-ns2", stats[0].Namespace) require.Equal(t, "group", stats[0].Group) require.Equal(t, "resource1", stats[0].Resource) require.Equal(t, int64(2), stats[0].Count) @@ -293,11 +289,11 @@ func runTestIntegrationBackendGetResourceStats(t *testing.T, backend resource.St }) t.Run("Get stats with minimum count", func(t *testing.T) { - stats, err := backend.GetResourceStats(ctx, nsPrefix+"-ns1", 1) + stats, err := backend.GetResourceStats(ctx, nsPrefix+"-stats-ns1", 1) require.NoError(t, err) require.Len(t, stats, 1) - require.Equal(t, nsPrefix+"-ns1", stats[0].Namespace) + require.Equal(t, nsPrefix+"-stats-ns1", stats[0].Namespace) require.Equal(t, "group", stats[0].Group) require.Equal(t, "resource1", stats[0].Resource) require.Equal(t, int64(2), stats[0].Count) @@ -314,7 +310,7 @@ func runTestIntegrationBackendWatchWriteEvents(t *testing.T, backend resource.St ctx := testutil.NewTestContext(t, time.Now().Add(5*time.Second)) // Create a few resources before initing the watch - _, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(nsPrefix+"-ns1")) + _, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(nsPrefix+"-watch-ns")) require.NoError(t, err) // Start the watch @@ -322,7 +318,7 @@ func runTestIntegrationBackendWatchWriteEvents(t *testing.T, backend resource.St require.NoError(t, err) // Create one more event - _, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(nsPrefix+"-ns1")) + _, err = writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(nsPrefix+"-watch-ns")) require.NoError(t, err) require.Equal(t, "item2", (<-stream).Key.Name) @@ -336,7 +332,7 @@ func runTestIntegrationBackendWatchWriteEvents(t *testing.T, backend resource.St func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(5*time.Second)) server := newServer(t, backend) - ns := nsPrefix + "-ns1" + ns := nsPrefix + "-list-ns" // Create a few resources before starting the watch rv1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns)) require.NoError(t, err) @@ -367,8 +363,9 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend res, err := server.List(ctx, &resourcepb.ListRequest{ Options: &resourcepb.ListOptions{ Key: &resourcepb.ResourceKey{ - Group: "group", - Resource: "resource", + Namespace: ns, + Group: "group", + Resource: "resource", }, }, }) @@ -390,8 +387,9 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend Limit: 3, Options: &resourcepb.ListOptions{ Key: &resourcepb.ResourceKey{ - Group: "group", - Resource: "resource", + Namespace: ns, + Group: "group", + Resource: "resource", }, }, }) @@ -411,8 +409,9 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend ResourceVersion: rv4, Options: &resourcepb.ListOptions{ Key: &resourcepb.ResourceKey{ - Group: "group", - Resource: "resource", + Namespace: ns, + Group: "group", + Resource: "resource", }, }, }) @@ -432,8 +431,9 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend ResourceVersion: rv7, Options: &resourcepb.ListOptions{ Key: &resourcepb.ResourceKey{ - Group: "group", - Resource: "resource", + Namespace: ns, + Group: "group", + Resource: "resource", }, }, }) @@ -462,8 +462,9 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend Limit: 2, Options: &resourcepb.ListOptions{ Key: &resourcepb.ResourceKey{ - Group: "group", - Resource: "resource", + Namespace: ns, + Group: "group", + Resource: "resource", }, }, }) @@ -484,7 +485,7 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend func runTestIntegrationBackendListHistory(t *testing.T, backend resource.StorageBackend, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) server := newServer(t, backend) - ns := nsPrefix + "-ns1" + ns := nsPrefix + "-history-ns" rv1, _ := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns)) require.Greater(t, rv1, int64(0)) @@ -985,12 +986,12 @@ func runTestIntegrationBackendCreateNewResource(t *testing.T, backend resource.S request := &resourcepb.CreateRequest{ Key: &resourcepb.ResourceKey{ - Namespace: "default", + Namespace: ns, Group: "test.grafana", - Resource: "Test", + Resource: "tests", Name: "test", }, - Value: []byte(`{"apiVersion":"test.grafana/v0alpha1","kind":"Test","metadata":{"name":"test","namespace":"default"}}`), + Value: []byte(`{"apiVersion":"test.grafana/v0alpha1","kind":"Test","metadata":{"name":"test","namespace":"` + ns + `","uid":"test-uid-123"}}`), } response, err := server.Create(ctx, request) diff --git a/pkg/storage/unified/testing/storage_backend_test.go b/pkg/storage/unified/testing/storage_backend_test.go new file mode 100644 index 00000000000..a1da3b82e72 --- /dev/null +++ b/pkg/storage/unified/testing/storage_backend_test.go @@ -0,0 +1,29 @@ +package test + +import ( + "context" + "testing" + + badger "github.com/dgraph-io/badger/v4" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/storage/unified/resource" +) + +func TestBadgerKVStorageBackend(t *testing.T) { + RunStorageBackendTest(t, func(ctx context.Context) resource.StorageBackend { + opts := badger.DefaultOptions("").WithInMemory(true).WithLogger(nil) + db, err := badger.Open(opts) + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + return resource.NewKvStorageBackend(resource.NewBadgerKV(db)) + }, &TestOptions{ + NSPrefix: "kvstorage-test", + SkipTests: map[string]bool{ + // TODO: fix these tests and remove this skip + TestBlobSupport: true, + }, + }) +} diff --git a/pkg/tests/apis/secret/keeper_test.go b/pkg/tests/apis/secret/keeper_test.go deleted file mode 100644 index c81a8544ba7..00000000000 --- a/pkg/tests/apis/secret/keeper_test.go +++ /dev/null @@ -1,567 +0,0 @@ -package secret - -import ( - "context" - "errors" - "math/rand/v2" - "net/http" - "strconv" - "strings" - "testing" - - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var gvrKeepers = schema.GroupVersionResource{ - Group: secretv0alpha1.GROUP, - Version: secretv0alpha1.VERSION, - Resource: secretv0alpha1.KeeperResourceInfo.GetName(), -} - -func TestIntegrationKeeper(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs - EnableFeatureToggles: []string{ - // Required to start the example service - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, - featuremgmt.FlagSecretsManagementAppPlatform, - }, - }) - - permissions := map[string]ResourcePermission{ResourceKeepers: {Actions: ActionsAllKeepers}} - - genericUserEditor := mustCreateUsers(t, helper, permissions).Editor - - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: genericUserEditor, - GVR: gvrKeepers, - }) - - t.Run("reading a keeper that does not exist returns a 404", func(t *testing.T) { - raw, err := client.Resource.Get(ctx, "some-keeper-that-does-not-exist", metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, "keeper.secret.grafana.app \"some-keeper-that-does-not-exist\" not found", err.Error()) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - t.Run("deleting a keeper that does not exist returns an error", func(t *testing.T) { - err := client.Resource.Delete(ctx, "some-keeper-that-does-not-exist", metav1.DeleteOptions{}) - require.Error(t, err) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, "keeper.secret.grafana.app \"some-keeper-that-does-not-exist\" not found", err.Error()) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - t.Run("creating a keeper returns it", func(t *testing.T) { - raw := mustGenerateKeeper(t, helper, genericUserEditor, nil, "testdata/keeper-aws-generate.yaml") - - keeper := new(secretv0alpha1.Keeper) - err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, keeper) - require.NoError(t, err) - require.NotNil(t, keeper) - - require.NotEmpty(t, keeper.Spec.Description) - require.NotEmpty(t, keeper.Spec.AWS) - require.Empty(t, keeper.Spec.Azure) - - t.Run("and creating another keeper with the same name in the same namespace returns an error", func(t *testing.T) { - testKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - testKeeper.SetName(raw.GetName()) - - raw, err := client.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - }) - - t.Run("and reading th keeper returns it same as if when it was created", func(t *testing.T) { - raw, err := client.Resource.Get(ctx, keeper.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - anotherKeeper := new(secretv0alpha1.Keeper) - err = runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, anotherKeeper) - require.NoError(t, err) - require.NotNil(t, anotherKeeper) - - require.EqualValues(t, keeper, anotherKeeper) - }) - - t.Run("and listing keepers returns the created keeper", func(t *testing.T) { - rawList, err := client.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.GreaterOrEqual(t, len(rawList.Items), 1) - require.Equal(t, keeper.Name, rawList.Items[0].GetName()) - }) - - t.Run("and updating the keeper replaces the spec fields and returns them", func(t *testing.T) { - newRaw := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - newRaw.SetName(raw.GetName()) - newRaw.Object["spec"].(map[string]any)["description"] = "New description" - newRaw.Object["metadata"].(map[string]any)["annotations"] = map[string]any{"newAnnotation": "newValue"} - - updatedRaw, err := client.Resource.Update(ctx, newRaw, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, updatedRaw) - - updatedKeeper := new(secretv0alpha1.Keeper) - err = runtime.DefaultUnstructuredConverter.FromUnstructured(updatedRaw.Object, updatedKeeper) - require.NoError(t, err) - require.NotNil(t, updatedKeeper) - - require.NotEqualValues(t, updatedKeeper.Spec, keeper.Spec) - }) - - t.Run("and updating the keeper to reference securevalues that does not exist returns an error", func(t *testing.T) { - newRaw := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - newRaw.SetName(raw.GetName()) - newRaw.Object["spec"].(map[string]any)["aws"] = map[string]any{ - "accessKeyId": map[string]any{ - "secureValueName": "securevalue-does-not-exist-1", - }, - "secretAccessKey": map[string]any{ - "secureValueName": "securevalue-does-not-exist-2", - }, - } - - updatedRaw, err := client.Resource.Update(ctx, newRaw, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, updatedRaw) - require.Contains(t, err.Error(), "securevalue-does-not-exist-1") - require.Contains(t, err.Error(), "securevalue-does-not-exist-2") - }) - }) - - t.Run("creating an invalid keeper fails validation and returns an error", func(t *testing.T) { - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.Object["spec"].(map[string]any)["description"] = "" - - raw, err := client.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - }) - - t.Run("creating a keeper with a provider then changing the provider does not return an error", func(t *testing.T) { - rawAWS := mustGenerateKeeper(t, helper, genericUserEditor, nil, "testdata/keeper-aws-generate.yaml") - - testDataKeeperGCP := rawAWS.DeepCopy() - testDataKeeperGCP.Object["spec"].(map[string]any)["aws"] = nil - testDataKeeperGCP.Object["spec"].(map[string]any)["gcp"] = map[string]any{ - "projectId": "project-id", - "credentialsFile": "/path/to/file.json", - } - - rawGCP, err := client.Resource.Update(ctx, testDataKeeperGCP, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGCP) - - require.NotEqualValues(t, rawAWS.Object["spec"], rawGCP.Object["spec"]) - }) - - t.Run("creating a keeper that references securevalues that does not exist returns an error", func(t *testing.T) { - testDataKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testDataKeeper.Object["spec"].(map[string]any)["aws"] = map[string]any{ - "accessKeyId": map[string]any{ - "secureValueName": "securevalue-does-not-exist-1", - }, - "secretAccessKey": map[string]any{ - "secureValueName": "securevalue-does-not-exist-2", - }, - } - - raw, err := client.Resource.Create(ctx, testDataKeeper, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.Contains(t, err.Error(), "securevalue-does-not-exist-1") - require.Contains(t, err.Error(), "securevalue-does-not-exist-2") - }) - - t.Run("deleting a keeper that exists does not return an error", func(t *testing.T) { - generatePrefix := "generated-" - - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetGenerateName(generatePrefix) - - raw, err := client.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - name := raw.GetName() - require.True(t, strings.HasPrefix(name, generatePrefix)) - - err = client.Resource.Delete(ctx, name, metav1.DeleteOptions{}) - require.NoError(t, err) - - t.Run("and then trying to read it returns a 404 error", func(t *testing.T) { - raw, err := client.Resource.Get(ctx, name, metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - t.Run("and listing keepers returns an empty list", func(t *testing.T) { - rawList, err := client.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.Empty(t, rawList.Items) - }) - }) - - t.Run("creating keepers in multiple namespaces", func(t *testing.T) { - permissions := map[string]ResourcePermission{ - ResourceKeepers: {Actions: ActionsAllKeepers}, - } - - editorOrgA := mustCreateUsers(t, helper, permissions).Editor - editorOrgB := mustCreateUsers(t, helper, permissions).Editor - - keeperOrgA := mustGenerateKeeper(t, helper, editorOrgA, nil, "testdata/keeper-aws-generate.yaml") - keeperOrgB := mustGenerateKeeper(t, helper, editorOrgB, nil, "testdata/keeper-aws-generate.yaml") - - clientOrgA := helper.GetResourceClient(apis.ResourceClientArgs{User: editorOrgA, GVR: gvrKeepers}) - clientOrgB := helper.GetResourceClient(apis.ResourceClientArgs{User: editorOrgB, GVR: gvrKeepers}) - - // Create - t.Run("creating a keeper with the same name as one from another namespace does not return an error", func(t *testing.T) { - // OrgA creating a keeper with the same name from OrgB. - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgB.GetName()) - - raw, err := clientOrgA.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - // OrgA creating a keeper with the same name from OrgB. - testData = helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgA.GetName()) - - raw, err = clientOrgB.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - require.NoError(t, clientOrgA.Resource.Delete(ctx, keeperOrgB.GetName(), metav1.DeleteOptions{})) - require.NoError(t, clientOrgB.Resource.Delete(ctx, keeperOrgA.GetName(), metav1.DeleteOptions{})) - }) - - // Read - t.Run("fetching a keeper from another namespace returns not found", func(t *testing.T) { - var statusErr *apierrors.StatusError - - // OrgA trying to fetch keeper from OrgB. - raw, err := clientOrgA.Resource.Get(ctx, keeperOrgB.GetName(), metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // OrgB trying to fetch keeper from OrgA. - raw, err = clientOrgB.Resource.Get(ctx, keeperOrgA.GetName(), metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - // Update - t.Run("updating a keeper from another namespace returns not found", func(t *testing.T) { - var statusErr *apierrors.StatusError - - // OrgA trying to update securevalue from OrgB. - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgB.GetName()) - testData.Object["spec"].(map[string]any)["description"] = "New description" - - raw, err := clientOrgA.Resource.Update(ctx, testData, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // OrgB trying to update keeper from OrgA. - testData = helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgA.GetName()) - testData.Object["spec"].(map[string]any)["description"] = "New description" - - raw, err = clientOrgB.Resource.Update(ctx, testData, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - // Delete - t.Run("deleting a keeper from another namespace returns an error and does not delete it", func(t *testing.T) { - var statusErr *apierrors.StatusError - - // OrgA trying to delete keeper from OrgB. - err := clientOrgA.Resource.Delete(ctx, keeperOrgB.GetName(), metav1.DeleteOptions{}) - require.Error(t, err) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // Check that it still exists from the perspective of OrgB. - raw, err := clientOrgB.Resource.Get(ctx, keeperOrgB.GetName(), metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - // OrgB trying to delete keeper from OrgA. - err = clientOrgB.Resource.Delete(ctx, keeperOrgA.GetName(), metav1.DeleteOptions{}) - require.Error(t, err) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // Check that it still exists from the perspective of OrgA. - raw, err = clientOrgA.Resource.Get(ctx, keeperOrgA.GetName(), metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - }) - - // List - t.Run("listing keeper from a namespace does not return the ones from another namespace", func(t *testing.T) { - // OrgA listing keeper. - listOrgA, err := clientOrgA.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, listOrgA) - require.Len(t, listOrgA.Items, 1) - require.Equal(t, *keeperOrgA, listOrgA.Items[0]) - - // OrgB listing keeper. - listOrgB, err := clientOrgB.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, listOrgB) - require.Len(t, listOrgB.Items, 1) - require.Equal(t, *keeperOrgB, listOrgB.Items[0]) - }) - }) - - t.Run("keeper actions without having required permissions", func(t *testing.T) { - // Create users on a random org without specifying secrets-related permissions. - editorP := mustCreateUsers(t, helper, nil).Editor - - clientP := helper.GetResourceClient(apis.ResourceClientArgs{ - User: editorP, - GVR: gvrKeepers, - }) - - // GET - rawGet, err := clientP.Resource.Get(ctx, "some-keeper", metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, rawGet) - - var statusGetErr *apierrors.StatusError - require.True(t, errors.As(err, &statusGetErr)) - require.EqualValues(t, http.StatusForbidden, statusGetErr.Status().Code) - - // LIST - rawList, err := clientP.Resource.List(ctx, metav1.ListOptions{}) - require.Error(t, err) - require.Nil(t, rawList) - - var statusListErr *apierrors.StatusError - require.True(t, errors.As(err, &statusListErr)) - require.EqualValues(t, http.StatusForbidden, statusListErr.Status().Code) - - // CREATE - testKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") // to pass validation before authz. - rawCreate, err := clientP.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, rawCreate) - - var statusCreateErr *apierrors.StatusError - require.True(t, errors.As(err, &statusCreateErr)) - require.EqualValues(t, http.StatusForbidden, statusCreateErr.Status().Code) - - // UPDATE - testKeeper.SetName("test") // to pass validation before authz. - rawUpdate, err := clientP.Resource.Update(ctx, testKeeper, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, rawUpdate) - - var statusUpdateErr *apierrors.StatusError - require.True(t, errors.As(err, &statusUpdateErr)) - require.EqualValues(t, http.StatusForbidden, statusUpdateErr.Status().Code) - - // DELETE - err = clientP.Resource.Delete(ctx, "some-keeper", metav1.DeleteOptions{}) - require.Error(t, err) - - var statusDeleteErr *apierrors.StatusError - require.True(t, errors.As(err, &statusDeleteErr)) - require.EqualValues(t, http.StatusForbidden, statusDeleteErr.Status().Code) - }) - - t.Run("keeper actions with permissions but with limited scope", func(t *testing.T) { - suffix := strconv.FormatInt(rand.Int64(), 10) - - // Fix the Keeper names. - keeperName := "kp-" + suffix - testKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - testKeeper.SetName(keeperName) - - keeperNameAnother := "kp-another-" + suffix - testKeeperAnother := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - testKeeperAnother.SetName(keeperNameAnother) - - // Fix the org ID because we will create another user with scope "all" permissions on the same org, to compare. - orgID := rand.Int64() + 2 - - // Permissions which allow any action, but scoped actions (get, update, delete) only on `keeperName` and NO OTHER Keeper. - scopedLimitedPermissions := map[string]ResourcePermission{ - ResourceKeepers: { - Actions: ActionsAllKeepers, - Name: keeperName, - }, - } - - // Create users (+ client) with permission to manage ONLY the Keeper `keeperName`. - editorLimited := mustCreateUsersWithOrg(t, helper, orgID, scopedLimitedPermissions).Editor - - clientScopedLimited := helper.GetResourceClient(apis.ResourceClientArgs{ - User: editorLimited, - GVR: gvrKeepers, - }) - - // Create users (+ client) with permission to manage ANY Keepers. - scopedAllPermissions := map[string]ResourcePermission{ - ResourceKeepers: { - Actions: ActionsAllKeepers, - Name: "*", // this or not sending a `Name` have the same effect. - }, - } - - editorAll := mustCreateUsersWithOrg(t, helper, orgID, scopedAllPermissions).Editor - - clientScopedAll := helper.GetResourceClient(apis.ResourceClientArgs{ - User: editorAll, - GVR: gvrKeepers, - }) - - // For create, we don't have actual granular permissions, so we can use any client that has unscoped create permissions. - // This is because when we don't know yet what the name of the resource will be, the request comes with an empty value. - // And thus the authorizer can't do granular checks. - t.Run("CREATE", func(t *testing.T) { - rawCreateLimited, err := clientScopedAll.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawCreateLimited) - - rawCreateLimited, err = clientScopedAll.Resource.Create(ctx, testKeeperAnother, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawCreateLimited) - }) - - t.Run("READ", func(t *testing.T) { - // Retrieve `keeperName` from the limited client. - rawGetLimited, err := clientScopedLimited.Resource.Get(ctx, keeperName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGetLimited) - require.Equal(t, rawGetLimited.GetUID(), rawGetLimited.GetUID()) - - // Retrieve `keeperName` from the scope-all client. - rawGetAll, err := clientScopedAll.Resource.Get(ctx, keeperName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGetAll) - require.Equal(t, rawGetAll.GetUID(), rawGetLimited.GetUID()) - - // Even though we can create it, we cannot retrieve `keeperNameAnother` from the limited client. - rawGetLimited, err = clientScopedLimited.Resource.Get(ctx, keeperNameAnother, metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, rawGetLimited) - - var statusGetErr *apierrors.StatusError - require.True(t, errors.As(err, &statusGetErr)) - require.EqualValues(t, http.StatusForbidden, statusGetErr.Status().Code) - - // Retrieve `keeperNameAnother` from the scope-all client. - rawGetAll, err = clientScopedAll.Resource.Get(ctx, keeperNameAnother, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGetAll) - }) - - t.Run("LIST", func(t *testing.T) { - // List Keepers from the limited client should return only 1. - rawList, err := clientScopedLimited.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.Len(t, rawList.Items, 1) - require.Equal(t, keeperName, rawList.Items[0].GetName()) - - // List Keepers from the scope-all client should return all of them. - rawList, err = clientScopedAll.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.Len(t, rawList.Items, 2) - }) - - t.Run("UPDATE", func(t *testing.T) { - // Update `keeperName` from the limited client. - testKeeperUpdate := testKeeper.DeepCopy() - testKeeperUpdate.Object["spec"].(map[string]any)["description"] = "keeper-description-1234" - - rawUpdate, err := clientScopedLimited.Resource.Update(ctx, testKeeperUpdate, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawUpdate) - - // Try to update `keeperNameAnother` from the limited client. - testKeeperAnotherUpdate := testKeeperAnother.DeepCopy() - testKeeperAnotherUpdate.Object["spec"].(map[string]any)["description"] = "keeper-description-5678" - - rawUpdate, err = clientScopedLimited.Resource.Update(ctx, testKeeperAnotherUpdate, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, rawUpdate) - - var statusUpdateErr *apierrors.StatusError - require.True(t, errors.As(err, &statusUpdateErr)) - require.EqualValues(t, http.StatusForbidden, statusUpdateErr.Status().Code) - - // Update `keeperNameAnother` from the scope-all client. - rawUpdate, err = clientScopedAll.Resource.Update(ctx, testKeeperAnotherUpdate, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawUpdate) - }) - - // Keep this last for cleaning up the resources. - t.Run("DELETE", func(t *testing.T) { - // Try to delete `keeperNameAnother` from the limited client. - err := clientScopedLimited.Resource.Delete(ctx, keeperNameAnother, metav1.DeleteOptions{}) - require.Error(t, err) - - var statusDeleteErr *apierrors.StatusError - require.True(t, errors.As(err, &statusDeleteErr)) - require.EqualValues(t, http.StatusForbidden, statusDeleteErr.Status().Code) - - // Delete `keeperNameAnother` from the scope-all client. - err = clientScopedAll.Resource.Delete(ctx, keeperNameAnother, metav1.DeleteOptions{}) - require.NoError(t, err) - - // Delete `keeperName` from the limited client. - err = clientScopedLimited.Resource.Delete(ctx, keeperName, metav1.DeleteOptions{}) - require.NoError(t, err) - }) - }) -} diff --git a/pkg/tests/apis/secret/main_test.go b/pkg/tests/apis/secret/main_test.go deleted file mode 100644 index 20169fdbaa2..00000000000 --- a/pkg/tests/apis/secret/main_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package secret - -import ( - "cmp" - "context" - "encoding/json" - "math/rand/v2" - "strconv" - "testing" - - "github.com/grafana/grafana/pkg/registry/apis/secret" - "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/team" - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -var ( - ResourceSecureValues = "secret.securevalues" - ResourceKeepers = "secret.keepers" - - ActionsAllKeepers = []string{ - secret.ActionSecretKeepersCreate, - secret.ActionSecretKeepersWrite, - secret.ActionSecretKeepersRead, - secret.ActionSecretKeepersDelete, - } - ActionsAllSecureValues = []string{ - secret.ActionSecretSecureValuesCreate, - secret.ActionSecretSecureValuesWrite, - secret.ActionSecretSecureValuesRead, - secret.ActionSecretSecureValuesDelete, - } -) - -type ResourcePermission struct { - Actions []string - Name string // empty or "*" for all -} - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -func TestIntegrationDiscoveryClient(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs - EnableFeatureToggles: []string{ - // Required to start the example service - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, - featuremgmt.FlagSecretsManagementAppPlatform, - }, - }) - - t.Run("check discovery client", func(t *testing.T) { - disco := helper.NewDiscoveryClient() - - resources, err := disco.ServerResourcesForGroupVersion("secret.grafana.app/v0alpha1") - require.NoError(t, err) - - v1Disco, err := json.MarshalIndent(resources, "", " ") - require.NoError(t, err) - - var apiResourceList map[string]any - require.NoError(t, json.Unmarshal(v1Disco, &apiResourceList)) - - groupVersion, ok := apiResourceList["groupVersion"].(string) - require.True(t, ok) - require.Equal(t, "secret.grafana.app/v0alpha1", groupVersion) - - apiResources, ok := apiResourceList["resources"].([]any) - require.True(t, ok) - require.Len(t, apiResources, 2) // securevalue + keeper + (subresources...) - }) -} - -func mustCreateUsersWithOrg(t *testing.T, helper *apis.K8sTestHelper, orgID int64, permissionMap map[string]ResourcePermission) apis.OrgUsers { - t.Helper() - - permissions := make([]resourcepermissions.SetResourcePermissionCommand, 0, len(permissionMap)) - for resource, permission := range permissionMap { - permissions = append(permissions, resourcepermissions.SetResourcePermissionCommand{ - Actions: permission.Actions, - Resource: resource, - ResourceAttribute: "uid", - ResourceID: cmp.Or(permission.Name, "*"), - }) - } - - orgName := "org-" + strconv.FormatInt(orgID, 10) - - userSuffix := strconv.FormatInt(rand.Int64(), 10) - - // Add here admin or viewer if necessary. - editor := helper.CreateUser("editor-"+userSuffix, orgName, org.RoleEditor, permissions) - - staff := helper.CreateTeam("staff-"+userSuffix, "staff-"+userSuffix+"@"+orgName, editor.Identity.GetOrgID()) - - // Also call this method for each new user. - helper.AddOrUpdateTeamMember(editor, staff.ID, team.PermissionTypeMember) - - return apis.OrgUsers{ - Editor: editor, - Staff: staff, - } -} - -func mustCreateUsers(t *testing.T, helper *apis.K8sTestHelper, permissionMap map[string]ResourcePermission) apis.OrgUsers { - orgID := rand.Int64() + 2 // if it is 0, becomes 2 and not 1. - return mustCreateUsersWithOrg(t, helper, orgID, permissionMap) -} - -func mustGenerateKeeper(t *testing.T, helper *apis.K8sTestHelper, user apis.User, specType map[string]any, testFile string) *unstructured.Unstructured { - t.Helper() - - require.NotEmpty(t, testFile, "testFile must not be empty") - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - keeperClient := helper.GetResourceClient(apis.ResourceClientArgs{ - User: user, - GVR: gvrKeepers, - }) - - testKeeper := helper.LoadYAMLOrJSONFile(testFile) - if specType != nil { - testKeeper.Object["spec"] = specType - } - - raw, err := keeperClient.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - t.Cleanup(func() { - require.NoError(t, keeperClient.Resource.Delete(ctx, raw.GetName(), metav1.DeleteOptions{})) - }) - - return raw -} diff --git a/pkg/tests/apis/secret/testdata/keeper-aws-generate.yaml b/pkg/tests/apis/secret/testdata/keeper-aws-generate.yaml deleted file mode 100644 index c210ab66838..00000000000 --- a/pkg/tests/apis/secret/testdata/keeper-aws-generate.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: Keeper -metadata: - annotations: - xx: XXX - labels: - aa: AAA -spec: - description: AWS XYZ value - aws: - accessKeyId: - valueFromEnv: ACCESS_KEY_ID_XYZ - secretAccessKey: - valueFromEnv: SECRET_ACCESS_KEY_XYZ - kmsKeyId: kmsKeyId-xyz diff --git a/pkg/tests/apis/secret/testdata/keeper-gcp-generate.yaml b/pkg/tests/apis/secret/testdata/keeper-gcp-generate.yaml deleted file mode 100644 index 485a4cb2ea5..00000000000 --- a/pkg/tests/apis/secret/testdata/keeper-gcp-generate.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: Keeper -metadata: - annotations: - xx: XXX - yy: YYY - labels: - aa: AAA - bb: BBB -spec: - description: GCP XYZ value - gcp: - projectId: project-id - credentialsFile: /path/to/file.json diff --git a/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml b/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml deleted file mode 100644 index dec2cd8611b..00000000000 --- a/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: SecureValue -metadata: - annotations: - xx: XXX - yy: YYY - labels: - aa: AAA - bb: BBB -spec: - description: This is a secret - value: this is super duper secure - decrypters: - - k6 - - synthetic-monitoring diff --git a/pkg/tests/apis/secret/testdata/secure-value-generate.yaml b/pkg/tests/apis/secret/testdata/secure-value-generate.yaml deleted file mode 100644 index 158052350c9..00000000000 --- a/pkg/tests/apis/secret/testdata/secure-value-generate.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: SecureValue -metadata: - annotations: - xx: XXX - yy: YYY - labels: - aa: AAA - bb: BBB -spec: - description: XYZ value - keeper: my-keeper-1 - value: super duper secure - decrypters: - - k6 - - synthetic-monitoring diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx index 401843d80c3..b3e7f4e2347 100644 --- a/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import { ChangePasswordPage, Props } from './ChangePasswordPage'; diff --git a/public/app/core/components/Signup/SignupPage.test.tsx b/public/app/core/components/Signup/SignupPage.test.tsx index 167bd584491..d8947e55ffa 100644 --- a/public/app/core/components/Signup/SignupPage.test.tsx +++ b/public/app/core/components/Signup/SignupPage.test.tsx @@ -1,6 +1,6 @@ import { render, fireEvent, screen, waitFor, userEvent } from 'test/test-utils'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import { SignupPage } from './SignupPage'; diff --git a/public/app/core/components/TimelineChart/utils.ts b/public/app/core/components/TimelineChart/utils.ts index 7df47641af7..1b233d4a3ad 100644 --- a/public/app/core/components/TimelineChart/utils.ts +++ b/public/app/core/components/TimelineChart/utils.ts @@ -327,7 +327,7 @@ export function prepareTimelineFields( for (let i = 0; i < frame.fields.length; i++) { let f = frame.fields[i]; - if (f.type === FieldType.time) { + if (f.type === FieldType.time && typeof f.values[0] === 'number') { if (startFieldIdx === -1) { startFieldIdx = i; } else if (endFieldIdx === -1) { @@ -390,9 +390,11 @@ export function prepareTimelineFields( for (let field of frame.fields) { switch (field.type) { case FieldType.time: - isTimeseries = true; - hasTimeseries = true; - fields.push(field); + if (typeof field.values[0] === 'number') { + isTimeseries = true; + hasTimeseries = true; + fields.push(field); + } break; case FieldType.enum: case FieldType.number: diff --git a/public/app/core/navigation/__mocks__/routeProps.ts b/public/app/core/navigation/mocks/routeProps.ts similarity index 100% rename from public/app/core/navigation/__mocks__/routeProps.ts rename to public/app/core/navigation/mocks/routeProps.ts diff --git a/public/app/core/services/FetchQueue.test.ts b/public/app/core/services/FetchQueue.test.ts index a7a93371e6d..af4e8eaaa90 100644 --- a/public/app/core/services/FetchQueue.test.ts +++ b/public/app/core/services/FetchQueue.test.ts @@ -3,7 +3,7 @@ import { take } from 'rxjs/operators'; import { BackendSrvRequest } from '@grafana/runtime'; import { FetchQueue, FetchQueueUpdate, FetchStatus } from './FetchQueue'; -import { subscribeTester } from './__mocks__/subscribeTester'; +import { subscribeTester } from './mocks/subscribeTester'; describe('FetchQueue', () => { describe('add', () => { diff --git a/public/app/core/services/ResponseQueue.test.ts b/public/app/core/services/ResponseQueue.test.ts index 09a32847891..5a5bb30a7b9 100644 --- a/public/app/core/services/ResponseQueue.test.ts +++ b/public/app/core/services/ResponseQueue.test.ts @@ -5,7 +5,7 @@ import { BackendSrvRequest } from '@grafana/runtime'; import { FetchQueue, FetchQueueUpdate } from './FetchQueue'; import { ResponseQueue } from './ResponseQueue'; -import { subscribeTester } from './__mocks__/subscribeTester'; +import { subscribeTester } from './mocks/subscribeTester'; const getTestContext = () => { const id = 'id'; diff --git a/public/app/core/services/__mocks__/search_srv.ts b/public/app/core/services/mocks/search_srv.ts similarity index 100% rename from public/app/core/services/__mocks__/search_srv.ts rename to public/app/core/services/mocks/search_srv.ts diff --git a/public/app/core/services/__mocks__/subscribeTester.ts b/public/app/core/services/mocks/subscribeTester.ts similarity index 100% rename from public/app/core/services/__mocks__/subscribeTester.ts rename to public/app/core/services/mocks/subscribeTester.ts diff --git a/public/app/core/utils/shortLinks.test.ts b/public/app/core/utils/shortLinks.test.ts index 1e68c787edb..38d459b906a 100644 --- a/public/app/core/utils/shortLinks.test.ts +++ b/public/app/core/utils/shortLinks.test.ts @@ -1,6 +1,6 @@ import { LogRowModel } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { createLogRow } from 'app/features/logs/components/__mocks__/logRow'; +import { createLogRow } from 'app/features/logs/components/mocks/logRow'; import { createShortLink, createAndCopyShortLink, getLogsPermalinkRange } from './shortLinks'; diff --git a/public/app/features/admin/Users/OrgUsersTable.test.tsx b/public/app/features/admin/Users/OrgUsersTable.test.tsx index 8652688ab6e..ea64232081e 100644 --- a/public/app/features/admin/Users/OrgUsersTable.test.tsx +++ b/public/app/features/admin/Users/OrgUsersTable.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { OrgUser } from '../../../types'; -import { getMockUsers } from '../../users/__mocks__/userMocks'; +import { getMockUsers } from '../../users/mocks/userMocks'; import { OrgUsersTable, Props } from './OrgUsersTable'; diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx index 21d3c92b5b0..0363e772509 100644 --- a/public/app/features/alerting/unified/Receivers.test.tsx +++ b/public/app/features/alerting/unified/Receivers.test.tsx @@ -6,7 +6,7 @@ import { PROVISIONED_MIMIR_ALERTMANAGER_UID, mockDataSources, setupVanillaAlertmanagerServer, -} from 'app/features/alerting/unified/components/settings/__mocks__/server'; +} from 'app/features/alerting/unified/components/settings/mocks/server'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; diff --git a/public/app/features/alerting/unified/Settings.test.tsx b/public/app/features/alerting/unified/Settings.test.tsx index d086ba7663d..0d362c74b45 100644 --- a/public/app/features/alerting/unified/Settings.test.tsx +++ b/public/app/features/alerting/unified/Settings.test.tsx @@ -3,8 +3,8 @@ import { render } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; import SettingsPage from './Settings'; -import DataSourcesResponse from './components/settings/__mocks__/api/datasources.json'; -import { setupGrafanaManagedServer, withExternalOnlySetting } from './components/settings/__mocks__/server'; +import DataSourcesResponse from './components/settings/mocks/api/datasources.json'; +import { setupGrafanaManagedServer, withExternalOnlySetting } from './components/settings/mocks/server'; import { setupMswServer } from './mockApi'; import { grantUserRole } from './mocks'; diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index 1e6f0344865..87f1ebfff59 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -17,11 +17,11 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasourc import { ContactPoint } from './ContactPoint'; import { ContactPointsPageContents } from './ContactPoints'; -import setupMimirFlavoredServer from './__mocks__/mimirFlavoredServer'; +import { RECEIVER_META_KEY } from './constants'; +import setupMimirFlavoredServer from './mocks/mimirFlavoredServer'; import setupVanillaAlertmanagerFlavoredServer, { VANILLA_ALERTMANAGER_DATASOURCE_UID, -} from './__mocks__/vanillaAlertmanagerServer'; -import { RECEIVER_META_KEY } from './constants'; +} from './mocks/vanillaAlertmanagerServer'; import { ContactPointWithMetadata, ReceiverConfigWithMetadata, RouteReference } from './utils'; /** diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.mimir.config.mock.json b/public/app/features/alerting/unified/components/contact-points/mocks/alertmanager.mimir.config.mock.json similarity index 100% rename from public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.mimir.config.mock.json rename to public/app/features/alerting/unified/components/contact-points/mocks/alertmanager.mimir.config.mock.json diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.vanilla.mock.json b/public/app/features/alerting/unified/components/contact-points/mocks/alertmanager.vanilla.mock.json similarity index 100% rename from public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.vanilla.mock.json rename to public/app/features/alerting/unified/components/contact-points/mocks/alertmanager.vanilla.mock.json diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts b/public/app/features/alerting/unified/components/contact-points/mocks/mimirFlavoredServer.ts similarity index 100% rename from public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts rename to public/app/features/alerting/unified/components/contact-points/mocks/mimirFlavoredServer.ts diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/receivers.mock.json b/public/app/features/alerting/unified/components/contact-points/mocks/receivers.mock.json similarity index 100% rename from public/app/features/alerting/unified/components/contact-points/__mocks__/receivers.mock.json rename to public/app/features/alerting/unified/components/contact-points/mocks/receivers.mock.json diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts b/public/app/features/alerting/unified/components/contact-points/mocks/vanillaAlertmanagerServer.ts similarity index 100% rename from public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts rename to public/app/features/alerting/unified/components/contact-points/mocks/vanillaAlertmanagerServer.ts diff --git a/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx b/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx index 5c1592ca098..9a97530ac9a 100644 --- a/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx +++ b/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx @@ -16,7 +16,7 @@ import { PROVISIONED_MIMIR_ALERTMANAGER_UID, mockDataSources, setupVanillaAlertmanagerServer, -} from './__mocks__/server'; +} from './mocks/server'; const renderConfiguration = ( alertManagerSourceName: string, diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/grafana/config/api/v1/alerts.json b/public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/grafana/config/api/v1/alerts.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/grafana/config/api/v1/alerts.json rename to public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/grafana/config/api/v1/alerts.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/grafana/config/history.json b/public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/grafana/config/history.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/grafana/config/history.json rename to public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/grafana/config/history.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/provisioned/config/api/v1/alerts.json b/public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/provisioned/config/api/v1/alerts.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/provisioned/config/api/v1/alerts.json rename to public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/provisioned/config/api/v1/alerts.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/vanilla prometheus/api/v2/status.json b/public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/vanilla prometheus/api/v2/status.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/alertmanager/vanilla prometheus/api/v2/status.json rename to public/app/features/alerting/unified/components/settings/mocks/api/alertmanager/vanilla prometheus/api/v2/status.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/datasources.json b/public/app/features/alerting/unified/components/settings/mocks/api/datasources.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/datasources.json rename to public/app/features/alerting/unified/components/settings/mocks/api/datasources.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/v1/ngalert/admin_config.json b/public/app/features/alerting/unified/components/settings/mocks/api/v1/ngalert/admin_config.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/v1/ngalert/admin_config.json rename to public/app/features/alerting/unified/components/settings/mocks/api/v1/ngalert/admin_config.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/api/v1/ngalert/alertmanagers.json b/public/app/features/alerting/unified/components/settings/mocks/api/v1/ngalert/alertmanagers.json similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/api/v1/ngalert/alertmanagers.json rename to public/app/features/alerting/unified/components/settings/mocks/api/v1/ngalert/alertmanagers.json diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/server.ts b/public/app/features/alerting/unified/components/settings/mocks/server.ts similarity index 100% rename from public/app/features/alerting/unified/components/settings/__mocks__/server.ts rename to public/app/features/alerting/unified/components/settings/mocks/server.ts diff --git a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts index c02659dcab5..f8259260a01 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts @@ -1,7 +1,7 @@ import { HttpResponse, JsonBodyType, StrictResponse, http } from 'msw'; import { TemplatesTestPayload } from 'app/features/alerting/unified/api/templateApi'; -import receiversMock from 'app/features/alerting/unified/components/contact-points/__mocks__/receivers.mock.json'; +import receiversMock from 'app/features/alerting/unified/components/contact-points/mocks/receivers.mock.json'; import { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; import { defaultGrafanaAlertingConfigurationStatusResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { diff --git a/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts b/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts index 30fb633dbc4..35f46b98984 100644 --- a/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts +++ b/public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts @@ -20,7 +20,7 @@ import { BackendSrv } from 'app/core/services/backend_srv'; import { EXTERNAL_VANILLA_ALERTMANAGER_UID, mockDataSources, -} from 'app/features/alerting/unified/components/settings/__mocks__/server'; +} from 'app/features/alerting/unified/components/settings/mocks/server'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types'; diff --git a/public/app/features/annotations/executeAnnotationQuery.test.ts b/public/app/features/annotations/executeAnnotationQuery.test.ts index 65526e1d549..f91185c44d2 100644 --- a/public/app/features/annotations/executeAnnotationQuery.test.ts +++ b/public/app/features/annotations/executeAnnotationQuery.test.ts @@ -2,7 +2,7 @@ import { DataSourceApi, dateTime, DataQuery } from '@grafana/data'; import { PanelModel } from '../dashboard/state/PanelModel'; import { createDashboardModelFixture } from '../dashboard/state/__fixtures__/dashboardFixtures'; -import { TestQuery, getMockDataSource } from '../query/state/__mocks__/mockDataSource'; +import { TestQuery, getMockDataSource } from '../query/state/mocks/mockDataSource'; import { executeAnnotationQuery } from './executeAnnotationQuery'; import { AnnotationQueryOptions } from './types'; diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx index 42995857cde..16f611e585d 100644 --- a/public/app/features/connections/Connections.test.tsx +++ b/public/app/features/connections/Connections.test.tsx @@ -3,15 +3,15 @@ import { Route, Routes } from 'react-router-dom-v5-compat'; import { render } from 'test/test-utils'; import { contextSrv } from 'app/core/services/context_srv'; -import { getMockDataSources } from 'app/features/datasources/__mocks__'; import * as api from 'app/features/datasources/api'; +import { getMockDataSources } from 'app/features/datasources/mocks/dataSourcesMocks'; import { configureStore } from 'app/store/configureStore'; -import { getPluginsStateMock } from '../plugins/admin/__mocks__'; +import { getPluginsStateMock } from '../plugins/admin/mocks/mockHelpers'; import Connections from './Connections'; -import { navIndex } from './__mocks__/store.navIndex.mock'; import { ROUTES } from './constants'; +import { navIndex } from './mocks/store.navIndex.mock'; jest.mock('app/core/services/context_srv'); jest.mock('app/features/datasources/api'); diff --git a/public/app/features/connections/__mocks__/store.navIndex.mock.ts b/public/app/features/connections/mocks/store.navIndex.mock.ts similarity index 100% rename from public/app/features/connections/__mocks__/store.navIndex.mock.ts rename to public/app/features/connections/mocks/store.navIndex.mock.ts diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx index 4267b7631d9..0215b463ca9 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx @@ -5,7 +5,7 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { PluginType } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { contextSrv } from 'app/core/core'; -import { getCatalogPluginMock, getPluginsStateMock } from 'app/features/plugins/admin/__mocks__'; +import { getCatalogPluginMock, getPluginsStateMock } from 'app/features/plugins/admin/mocks/mockHelpers'; import { CatalogPlugin } from 'app/features/plugins/admin/types'; import { AccessControlAction } from 'app/types'; diff --git a/public/app/features/correlations/CorrelationsPage.test.tsx b/public/app/features/correlations/CorrelationsPage.test.tsx index 3e88718ecbf..a57b901a2e4 100644 --- a/public/app/features/correlations/CorrelationsPage.test.tsx +++ b/public/app/features/correlations/CorrelationsPage.test.tsx @@ -31,7 +31,7 @@ import { createRemoveCorrelationResponse, createUpdateCorrelationResponse, MockDataSourceSrv, -} from './__mocks__/useCorrelations.mocks'; +} from './mocks/useCorrelations.mocks'; import { Correlation, CreateCorrelationParams, OmitUnion } from './types'; // Set app events up, otherwise plugin modules will fail to load diff --git a/public/app/features/correlations/__mocks__/useCorrelations.mocks.ts b/public/app/features/correlations/mocks/useCorrelations.mocks.ts similarity index 100% rename from public/app/features/correlations/__mocks__/useCorrelations.mocks.ts rename to public/app/features/correlations/mocks/useCorrelations.mocks.ts diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx index 97c3b1926d9..b2776dc9414 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx @@ -11,7 +11,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { LocationServiceProvider, config, locationService, setPluginImportUtils } from '@grafana/runtime'; import { VizPanel } from '@grafana/scenes'; import { Dashboard } from '@grafana/schema'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import store from 'app/core/store'; import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; diff --git a/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.test.tsx index 7d1371e2cdd..24ce8398e14 100644 --- a/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.test.tsx @@ -8,7 +8,7 @@ import { getPanelPlugin } from '@grafana/data/test'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { config, setPluginImportUtils, setRunRequest } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import { DashboardRoutes } from 'app/types/dashboard'; import { setupLoadDashboardMock, setupLoadDashboardMockReject } from '../utils/test-utils'; diff --git a/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts b/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts index 84a737d5a1d..4e4afe5d428 100644 --- a/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts +++ b/public/app/features/dashboard-scene/settings/version-history/HistorySrv.test.ts @@ -1,7 +1,7 @@ import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures'; import { HistorySrv } from './HistorySrv'; -import { restore, versions } from './__mocks__/dashboardHistoryMocks'; +import { restore, versions } from './mocks/dashboardHistoryMocks'; const getMock = jest.fn().mockResolvedValue({}); const postMock = jest.fn().mockResolvedValue({}); diff --git a/public/app/features/dashboard-scene/settings/version-history/__mocks__/dashboardHistoryMocks.ts b/public/app/features/dashboard-scene/settings/version-history/mocks/dashboardHistoryMocks.ts similarity index 100% rename from public/app/features/dashboard-scene/settings/version-history/__mocks__/dashboardHistoryMocks.ts rename to public/app/features/dashboard-scene/settings/version-history/mocks/dashboardHistoryMocks.ts diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx index f311d32d3f1..6fc2e3c5334 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx @@ -8,7 +8,7 @@ import { historySrv } from 'app/features/dashboard-scene/settings/version-histor import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures'; import { VersionsSettings, VERSIONS_FETCH_LIMIT } from './VersionsSettings'; -import { versions, diffs } from './__mocks__/versions'; +import { versions, diffs } from './mocks/versions'; jest.mock('app/features/dashboard-scene/settings/version-history/HistorySrv'); diff --git a/public/app/features/dashboard/components/DashboardSettings/__mocks__/versions.ts b/public/app/features/dashboard/components/DashboardSettings/mocks/versions.ts similarity index 100% rename from public/app/features/dashboard/components/DashboardSettings/__mocks__/versions.ts rename to public/app/features/dashboard/components/DashboardSettings/mocks/versions.ts diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx index 7eec5ebcb04..19e245cf03b 100644 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx @@ -11,7 +11,7 @@ import { config, setDataSourceSrv } from '@grafana/runtime'; import { Dashboard } from '@grafana/schema'; import { notifyApp } from 'app/core/actions'; import { AppChrome } from 'app/core/components/AppChrome/AppChrome'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import { RouteDescriptor } from 'app/core/navigation/types'; import { HOME_NAV_ID } from 'app/core/reducers/navModel'; import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types'; diff --git a/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx b/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx index b7e9e3ecf2e..6b3ebdcbee8 100644 --- a/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/PublicDashboardPage.test.tsx @@ -7,7 +7,7 @@ import { render } from 'test/test-utils'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { Dashboard, DashboardCursorSync, FieldConfigSource, Panel, ThresholdsMode } from '@grafana/schema/src'; -import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps'; import * as appTypes from 'app/types'; import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types'; diff --git a/public/app/features/dashboard/containers/SoloPanelPage.test.tsx b/public/app/features/dashboard/containers/SoloPanelPage.test.tsx index a1dfe58b99d..2a159054622 100644 --- a/public/app/features/dashboard/containers/SoloPanelPage.test.tsx +++ b/public/app/features/dashboard/containers/SoloPanelPage.test.tsx @@ -7,7 +7,7 @@ import { GrafanaContext } from 'app/core/context/GrafanaContext'; import { RouteDescriptor } from 'app/core/navigation/types'; import { DashboardMeta, DashboardRoutes } from 'app/types'; -import { getRouteComponentProps } from '../../../core/navigation/__mocks__/routeProps'; +import { getRouteComponentProps } from '../../../core/navigation/mocks/routeProps'; import { Props as DashboardPanelProps } from '../dashgrid/DashboardPanel'; import { DashboardModel } from '../state/DashboardModel'; import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures'; diff --git a/public/app/features/datasources/__mocks__/index.ts b/public/app/features/datasources/__mocks__/index.ts deleted file mode 100644 index 0485099d6c4..00000000000 --- a/public/app/features/datasources/__mocks__/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './dataSourcesMocks'; -export * from './store.navIndex.mock'; diff --git a/public/app/features/datasources/components/DataSourceDashboards.test.tsx b/public/app/features/datasources/components/DataSourceDashboards.test.tsx index 38e9a751267..8c5df1a680a 100644 --- a/public/app/features/datasources/components/DataSourceDashboards.test.tsx +++ b/public/app/features/datasources/components/DataSourceDashboards.test.tsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux'; import { configureStore } from 'app/store/configureStore'; -import { getMockDashboard } from '../__mocks__'; +import { getMockDashboard } from '../mocks/dataSourcesMocks'; import { DataSourceDashboardsView, ViewProps } from './DataSourceDashboards'; diff --git a/public/app/features/datasources/components/DataSourceTestingStatus.test.tsx b/public/app/features/datasources/components/DataSourceTestingStatus.test.tsx index 809e480b927..1c42b13b080 100644 --- a/public/app/features/datasources/components/DataSourceTestingStatus.test.tsx +++ b/public/app/features/datasources/components/DataSourceTestingStatus.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; -import { getMockDataSource } from '../__mocks__'; +import { getMockDataSource } from '../mocks/dataSourcesMocks'; import { DataSourceTestingStatus, Props } from './DataSourceTestingStatus'; diff --git a/public/app/features/datasources/components/DataSourcesList.test.tsx b/public/app/features/datasources/components/DataSourcesList.test.tsx index aff0285ecbe..d495695d0a7 100644 --- a/public/app/features/datasources/components/DataSourcesList.test.tsx +++ b/public/app/features/datasources/components/DataSourcesList.test.tsx @@ -1,7 +1,7 @@ import { screen } from '@testing-library/react'; import { render } from 'test/test-utils'; -import { getMockDataSources } from '../__mocks__'; +import { getMockDataSources } from '../mocks/dataSourcesMocks'; import { DataSourcesListView } from './DataSourcesList'; diff --git a/public/app/features/datasources/components/EditDataSource.test.tsx b/public/app/features/datasources/components/EditDataSource.test.tsx index 6a90180c43b..3631edc2e0a 100644 --- a/public/app/features/datasources/components/EditDataSource.test.tsx +++ b/public/app/features/datasources/components/EditDataSource.test.tsx @@ -7,7 +7,7 @@ import { setPluginComponentsHook } from '@grafana/runtime'; import { createComponentWithMeta } from 'app/features/plugins/extensions/usePluginComponents'; import { configureStore } from 'app/store/configureStore'; -import { getMockDataSource, getMockDataSourceMeta, getMockDataSourceSettingsState } from '../__mocks__'; +import { getMockDataSource, getMockDataSourceMeta, getMockDataSourceSettingsState } from '../mocks/dataSourcesMocks'; import { missingRightsMessage } from './DataSourceMissingRightsMessage'; import { readOnlyMessage } from './DataSourceReadOnlyMessage'; diff --git a/public/app/features/datasources/__mocks__/dataSourcesMocks.ts b/public/app/features/datasources/mocks/dataSourcesMocks.ts similarity index 100% rename from public/app/features/datasources/__mocks__/dataSourcesMocks.ts rename to public/app/features/datasources/mocks/dataSourcesMocks.ts diff --git a/public/app/features/datasources/__mocks__/store.navIndex.mock.ts b/public/app/features/datasources/mocks/store.navIndex.mock.ts similarity index 100% rename from public/app/features/datasources/__mocks__/store.navIndex.mock.ts rename to public/app/features/datasources/mocks/store.navIndex.mock.ts diff --git a/public/app/features/datasources/state/actions.test.ts b/public/app/features/datasources/state/actions.test.ts index 29be5fa5dba..f3b99a49bbc 100644 --- a/public/app/features/datasources/state/actions.test.ts +++ b/public/app/features/datasources/state/actions.test.ts @@ -6,9 +6,9 @@ import { appEvents } from 'app/core/core'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { ThunkResult, ThunkDispatch } from 'app/types'; -import { getMockDataSource } from '../__mocks__'; import * as api from '../api'; import { DATASOURCES_ROUTES } from '../constants'; +import { getMockDataSource } from '../mocks/dataSourcesMocks'; import { trackDataSourceCreated, trackDataSourceTested } from '../tracking'; import { GenericDataSourcePlugin } from '../types'; diff --git a/public/app/features/datasources/state/reducers.test.ts b/public/app/features/datasources/state/reducers.test.ts index ff8c49a4bbe..85f6455e837 100644 --- a/public/app/features/datasources/state/reducers.test.ts +++ b/public/app/features/datasources/state/reducers.test.ts @@ -3,7 +3,7 @@ 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__'; +import { getMockDataSource, getMockDataSources } from '../mocks/dataSourcesMocks'; import { GenericDataSourcePlugin } from '../types'; import { diff --git a/public/app/features/explore/Logs/LiveLogs.test.tsx b/public/app/features/explore/Logs/LiveLogs.test.tsx index 83fb5e13fb8..471c8ba1c65 100644 --- a/public/app/features/explore/Logs/LiveLogs.test.tsx +++ b/public/app/features/explore/Logs/LiveLogs.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import { LogRowModel } from '@grafana/data'; -import { makeLogs } from '../__mocks__/makeLogs'; +import { makeLogs } from '../mocks/makeLogs'; import { LiveLogsWithTheme } from './LiveLogs'; diff --git a/public/app/features/explore/Logs/PopoverMenu.test.tsx b/public/app/features/explore/Logs/PopoverMenu.test.tsx index 8ffa99a375c..4d94dca72a6 100644 --- a/public/app/features/explore/Logs/PopoverMenu.test.tsx +++ b/public/app/features/explore/Logs/PopoverMenu.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createLogRow } from '../../logs/components/__mocks__/logRow'; +import { createLogRow } from '../../logs/components/mocks/logRow'; import { PopoverMenu } from './PopoverMenu'; diff --git a/public/app/features/explore/__mocks__/data.ts b/public/app/features/explore/mocks/data.ts similarity index 100% rename from public/app/features/explore/__mocks__/data.ts rename to public/app/features/explore/mocks/data.ts diff --git a/public/app/features/explore/__mocks__/makeLogs.ts b/public/app/features/explore/mocks/makeLogs.ts similarity index 100% rename from public/app/features/explore/__mocks__/makeLogs.ts rename to public/app/features/explore/mocks/makeLogs.ts diff --git a/public/app/features/explore/__mocks__/mockData.ts b/public/app/features/explore/mocks/mockData.ts similarity index 100% rename from public/app/features/explore/__mocks__/mockData.ts rename to public/app/features/explore/mocks/mockData.ts diff --git a/public/app/features/explore/state/query.test.ts b/public/app/features/explore/state/query.test.ts index 2d4b4e7375e..8a4f3b96be0 100644 --- a/public/app/features/explore/state/query.test.ts +++ b/public/app/features/explore/state/query.test.ts @@ -24,7 +24,7 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import * as richHistory from '../../../core/utils/richHistory'; import { configureStore } from '../../../store/configureStore'; import { setTimeSrv, TimeSrv } from '../../dashboard/services/TimeSrv'; -import { makeLogs } from '../__mocks__/makeLogs'; +import { makeLogs } from '../mocks/makeLogs'; import { supplementaryQueryTypes } from '../utils/supplementaryQueries'; import { saveCorrelationsAction } from './explorePane'; diff --git a/public/app/features/explore/utils/supplementaryQueries.test.ts b/public/app/features/explore/utils/supplementaryQueries.test.ts index 6c231f4d206..8d274f162b1 100644 --- a/public/app/features/explore/utils/supplementaryQueries.test.ts +++ b/public/app/features/explore/utils/supplementaryQueries.test.ts @@ -21,7 +21,7 @@ import { DataQuery } from '@grafana/schema'; import { MockDataSourceApi } from '../../../../test/mocks/datasource_srv'; import { MockDataQueryRequest, MockQuery } from '../../../../test/mocks/query'; import { ExplorePanelData } from '../../../types'; -import { mockExplorePanelData } from '../__mocks__/data'; +import { mockExplorePanelData } from '../mocks/data'; import { getSupplementaryQueryProvider } from './supplementaryQueries'; diff --git a/public/app/features/explore/utils/supplementaryQueries_legacy.test.ts b/public/app/features/explore/utils/supplementaryQueries_legacy.test.ts index fbb79119bf8..6d6735a12c5 100644 --- a/public/app/features/explore/utils/supplementaryQueries_legacy.test.ts +++ b/public/app/features/explore/utils/supplementaryQueries_legacy.test.ts @@ -26,7 +26,7 @@ import { DataQuery } from '@grafana/schema'; import { MockDataSourceApi } from '../../../../test/mocks/datasource_srv'; import { MockDataQueryRequest, MockQuery } from '../../../../test/mocks/query'; import { ExplorePanelData } from '../../../types'; -import { mockExplorePanelData } from '../__mocks__/data'; +import { mockExplorePanelData } from '../mocks/data'; import { getSupplementaryQueryProvider } from './supplementaryQueries'; diff --git a/public/app/features/invites/InviteesTable.test.tsx b/public/app/features/invites/InviteesTable.test.tsx index 527c6f662be..262d2f1061c 100644 --- a/public/app/features/invites/InviteesTable.test.tsx +++ b/public/app/features/invites/InviteesTable.test.tsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux'; import { configureStore } from 'app/store/configureStore'; -import { getMockInvitees } from '../users/__mocks__/userMocks'; +import { getMockInvitees } from '../users/mocks/userMocks'; import InviteesTable from './InviteesTable'; diff --git a/public/app/features/invites/state/reducers.test.ts b/public/app/features/invites/state/reducers.test.ts index 9ac92e88f68..ace0ce019e9 100644 --- a/public/app/features/invites/state/reducers.test.ts +++ b/public/app/features/invites/state/reducers.test.ts @@ -1,6 +1,6 @@ import { keyBy } from 'lodash'; -import { getMockInvitees } from 'app/features/users/__mocks__/userMocks'; +import { getMockInvitees } from 'app/features/users/mocks/userMocks'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; diff --git a/public/app/features/logs/components/InfiniteScroll.test.tsx b/public/app/features/logs/components/InfiniteScroll.test.tsx index 90b10988e73..62cdecb329c 100644 --- a/public/app/features/logs/components/InfiniteScroll.test.tsx +++ b/public/app/features/logs/components/InfiniteScroll.test.tsx @@ -7,7 +7,7 @@ import { config } from '@grafana/runtime'; import { LogsSortOrder } from '@grafana/schema'; import { InfiniteScroll, Props, SCROLLING_THRESHOLD } from './InfiniteScroll'; -import { createLogRow } from './__mocks__/logRow'; +import { createLogRow } from './mocks/logRow'; const defaultTz = 'browser'; diff --git a/public/app/features/logs/components/LogDetails.test.tsx b/public/app/features/logs/components/LogDetails.test.tsx index 1ee66deb632..f8e7b6ff3bb 100644 --- a/public/app/features/logs/components/LogDetails.test.tsx +++ b/public/app/features/logs/components/LogDetails.test.tsx @@ -17,8 +17,8 @@ import { setPluginLinksHook } from '@grafana/runtime'; import { LogDetails, Props } from './LogDetails'; import { LOG_LINE_BODY_FIELD_NAME } from './LogDetailsBody'; -import { createLogRow } from './__mocks__/logRow'; import { getLogRowStyles } from './getLogRowStyles'; +import { createLogRow } from './mocks/logRow'; jest.mock('@grafana/runtime', () => { return { diff --git a/public/app/features/logs/components/LogDetailsRow.test.tsx b/public/app/features/logs/components/LogDetailsRow.test.tsx index 4463adde1f7..fe0b64bd749 100644 --- a/public/app/features/logs/components/LogDetailsRow.test.tsx +++ b/public/app/features/logs/components/LogDetailsRow.test.tsx @@ -4,7 +4,7 @@ import { ComponentProps } from 'react'; import { Field, CoreApp, FieldType, LinkModel } from '@grafana/data'; import { LogDetailsRow } from './LogDetailsRow'; -import { createLogRow } from './__mocks__/logRow'; +import { createLogRow } from './mocks/logRow'; type Props = ComponentProps; diff --git a/public/app/features/logs/components/LogRow.test.tsx b/public/app/features/logs/components/LogRow.test.tsx index dc28d2ee311..3760ebf4b1f 100644 --- a/public/app/features/logs/components/LogRow.test.tsx +++ b/public/app/features/logs/components/LogRow.test.tsx @@ -6,8 +6,8 @@ import tinycolor from 'tinycolor2'; import { CoreApp, createTheme, LogLevel, LogRowModel } from '@grafana/data'; import { LogRow } from './LogRow'; -import { createLogRow } from './__mocks__/logRow'; import { getLogRowStyles } from './getLogRowStyles'; +import { createLogRow } from './mocks/logRow'; const reportInteraction = jest.fn(); jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/features/logs/components/LogRowMessage.test.tsx b/public/app/features/logs/components/LogRowMessage.test.tsx index 80ad6abf91d..8d684bfaf90 100644 --- a/public/app/features/logs/components/LogRowMessage.test.tsx +++ b/public/app/features/logs/components/LogRowMessage.test.tsx @@ -6,8 +6,8 @@ import { CoreApp, createTheme, LogLevel, LogRowModel } from '@grafana/data'; import { IconButton } from '@grafana/ui'; import { LogRowMessage, MAX_CHARACTERS } from './LogRowMessage'; -import { createLogRow } from './__mocks__/logRow'; import { getLogRowStyles } from './getLogRowStyles'; +import { createLogRow } from './mocks/logRow'; const setup = (propOverrides?: Partial>, rowOverrides?: Partial) => { const theme = createTheme(); diff --git a/public/app/features/logs/components/LogRowMessageDisplayedFields.test.tsx b/public/app/features/logs/components/LogRowMessageDisplayedFields.test.tsx index 4e0bdfccf4a..1c9248a6650 100644 --- a/public/app/features/logs/components/LogRowMessageDisplayedFields.test.tsx +++ b/public/app/features/logs/components/LogRowMessageDisplayedFields.test.tsx @@ -6,8 +6,8 @@ import { IconButton } from '@grafana/ui'; import { LOG_LINE_BODY_FIELD_NAME } from './LogDetailsBody'; import { LogRowMessageDisplayedFields, Props } from './LogRowMessageDisplayedFields'; -import { createLogRow } from './__mocks__/logRow'; import { getLogRowStyles } from './getLogRowStyles'; +import { createLogRow } from './mocks/logRow'; const setup = (propOverrides: Partial = {}, detectedFields = ['place', 'planet']) => { const theme = createTheme(); diff --git a/public/app/features/logs/components/LogRows.test.tsx b/public/app/features/logs/components/LogRows.test.tsx index 35d5cc4d9ea..a79ac2787c0 100644 --- a/public/app/features/logs/components/LogRows.test.tsx +++ b/public/app/features/logs/components/LogRows.test.tsx @@ -6,7 +6,7 @@ import { LogRowModel, LogsDedupStrategy, LogsSortOrder } from '@grafana/data'; import { disablePopoverMenu, enablePopoverMenu, isPopoverMenuDisabled } from '../utils'; import { LogRows, Props } from './LogRows'; -import { createLogRow } from './__mocks__/logRow'; +import { createLogRow } from './mocks/logRow'; jest.mock('../utils', () => ({ ...jest.requireActual('../utils'), diff --git a/public/app/features/logs/components/logParser.test.ts b/public/app/features/logs/components/logParser.test.ts index 3ab9194d383..a132f75b642 100644 --- a/public/app/features/logs/components/logParser.test.ts +++ b/public/app/features/logs/components/logParser.test.ts @@ -3,8 +3,8 @@ import { mockTimeRange } from '@grafana/plugin-ui'; import { ExploreFieldLinkModel, getFieldLinksForExplore } from 'app/features/explore/utils/links'; import { GetFieldLinksFn } from 'app/plugins/panel/logs/types'; -import { createLogRow } from './__mocks__/logRow'; import { getAllFields, createLogLineLinks, FieldDef, getDataframeFields } from './logParser'; +import { createLogRow } from './mocks/logRow'; describe('logParser', () => { describe('getAllFields', () => { diff --git a/public/app/features/logs/components/__mocks__/logRow.ts b/public/app/features/logs/components/mocks/logRow.ts similarity index 100% rename from public/app/features/logs/components/__mocks__/logRow.ts rename to public/app/features/logs/components/mocks/logRow.ts diff --git a/public/app/features/logs/components/panel/LogLine.test.tsx b/public/app/features/logs/components/panel/LogLine.test.tsx index bdbbf74b2f2..1a099f950e9 100644 --- a/public/app/features/logs/components/panel/LogLine.test.tsx +++ b/public/app/features/logs/components/panel/LogLine.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { CoreApp, createTheme, LogsDedupStrategy, LogsSortOrder } from '@grafana/data'; import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody'; -import { createLogLine } from '../__mocks__/logRow'; +import { createLogLine } from '../mocks/logRow'; import { getGridTemplateColumns, getStyles, LogLine, Props } from './LogLine'; import { LogListFontSize } from './LogList'; diff --git a/public/app/features/logs/components/panel/LogLineMenu.test.tsx b/public/app/features/logs/components/panel/LogLineMenu.test.tsx index 0f8286f93ed..a447d81c62f 100644 --- a/public/app/features/logs/components/panel/LogLineMenu.test.tsx +++ b/public/app/features/logs/components/panel/LogLineMenu.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { CoreApp, createTheme, LogsDedupStrategy, LogsSortOrder } from '@grafana/data'; -import { createLogLine } from '../__mocks__/logRow'; +import { createLogLine } from '../mocks/logRow'; import { getStyles } from './LogLine'; import { LogLineMenu, LogLineMenuCustomItem } from './LogLineMenu'; diff --git a/public/app/features/logs/components/panel/LogList.test.tsx b/public/app/features/logs/components/panel/LogList.test.tsx index e0cf493bd67..17eda97f4f6 100644 --- a/public/app/features/logs/components/panel/LogList.test.tsx +++ b/public/app/features/logs/components/panel/LogList.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { CoreApp, getDefaultTimeRange, LogRowModel, LogsDedupStrategy, LogsSortOrder } from '@grafana/data'; import { disablePopoverMenu, enablePopoverMenu, isPopoverMenuDisabled } from '../../utils'; -import { createLogRow } from '../__mocks__/logRow'; +import { createLogRow } from '../mocks/logRow'; import { LogList, Props } from './LogList'; diff --git a/public/app/features/logs/components/panel/LogListContext.test.tsx b/public/app/features/logs/components/panel/LogListContext.test.tsx index efe2281d8b9..7469e157452 100644 --- a/public/app/features/logs/components/panel/LogListContext.test.tsx +++ b/public/app/features/logs/components/panel/LogListContext.test.tsx @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; import { ReactNode } from 'react'; -import { createLogLine } from '../__mocks__/logRow'; +import { createLogLine } from '../mocks/logRow'; import { useLogListContextData, diff --git a/public/app/features/logs/components/panel/LogListControls.test.tsx b/public/app/features/logs/components/panel/LogListControls.test.tsx index d33754400ff..f185bd51d7c 100644 --- a/public/app/features/logs/components/panel/LogListControls.test.tsx +++ b/public/app/features/logs/components/panel/LogListControls.test.tsx @@ -5,7 +5,7 @@ import { CoreApp, EventBusSrv, LogLevel, LogsDedupStrategy, LogsSortOrder } from import { config } from '@grafana/runtime'; import { downloadLogs } from '../../utils'; -import { createLogRow } from '../__mocks__/logRow'; +import { createLogRow } from '../mocks/logRow'; import { LogListFontSize } from './LogList'; import { LogListContextProvider } from './LogListContext'; diff --git a/public/app/features/logs/components/panel/grammar.test.ts b/public/app/features/logs/components/panel/grammar.test.ts index c50cafbbd4a..db218d73b5f 100644 --- a/public/app/features/logs/components/panel/grammar.test.ts +++ b/public/app/features/logs/components/panel/grammar.test.ts @@ -1,6 +1,6 @@ import Prism, { Token } from 'prismjs'; -import { createLogLine } from '../__mocks__/logRow'; +import { createLogLine } from '../mocks/logRow'; import { generateLogGrammar } from './grammar'; diff --git a/public/app/features/logs/components/panel/processing.test.ts b/public/app/features/logs/components/panel/processing.test.ts index 129d97451da..906d2f4a7c5 100644 --- a/public/app/features/logs/components/panel/processing.test.ts +++ b/public/app/features/logs/components/panel/processing.test.ts @@ -1,7 +1,7 @@ import { createTheme, Field, FieldType, LogLevel, LogRowModel, LogsSortOrder, toDataFrame } from '@grafana/data'; import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody'; -import { createLogLine, createLogRow } from '../__mocks__/logRow'; +import { createLogLine, createLogRow } from '../mocks/logRow'; import { LogListFontSize } from './LogList'; import { LogListModel, preProcessLogs } from './processing'; diff --git a/public/app/features/logs/components/panel/virtualization.test.ts b/public/app/features/logs/components/panel/virtualization.test.ts index ff1789ad50e..75534d4e170 100644 --- a/public/app/features/logs/components/panel/virtualization.test.ts +++ b/public/app/features/logs/components/panel/virtualization.test.ts @@ -1,7 +1,7 @@ import { createTheme, LogsSortOrder } from '@grafana/data'; import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody'; -import { createLogLine } from '../__mocks__/logRow'; +import { createLogLine } from '../mocks/logRow'; import { LogListModel, PreProcessOptions } from './processing'; import { LogLineVirtualization, getLogLineSize, DisplayOptions } from './virtualization'; diff --git a/public/app/features/logs/logsModel.test.ts b/public/app/features/logs/logsModel.test.ts index 5c62e8a07b4..f6221b6c6af 100644 --- a/public/app/features/logs/logsModel.test.ts +++ b/public/app/features/logs/logsModel.test.ts @@ -22,8 +22,8 @@ import { toDataFrame, } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { getMockFrames } from 'app/plugins/datasource/loki/__mocks__/frames'; import { LokiQueryDirection } from 'app/plugins/datasource/loki/dataquery.gen'; +import { getMockFrames } from 'app/plugins/datasource/loki/mocks/frames'; import { MockObservableDataSourceApi } from '../../../test/mocks/datasource_srv'; diff --git a/public/app/features/logs/utils.test.ts b/public/app/features/logs/utils.test.ts index 37597bb7d12..f23a8ae9069 100644 --- a/public/app/features/logs/utils.test.ts +++ b/public/app/features/logs/utils.test.ts @@ -9,7 +9,7 @@ import { MutableDataFrame, DataFrame, } from '@grafana/data'; -import { getMockFrames } from 'app/plugins/datasource/loki/__mocks__/frames'; +import { getMockFrames } from 'app/plugins/datasource/loki/mocks/frames'; import { logSeriesToLogsModel } from './logsModel'; import { diff --git a/public/app/features/plugins/admin/__mocks__/index.ts b/public/app/features/plugins/admin/__mocks__/index.ts deleted file mode 100644 index 3568b5d2a70..00000000000 --- a/public/app/features/plugins/admin/__mocks__/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as remotePluginMock } from './remotePlugin.mock'; -export { default as localPluginMock } from './localPlugin.mock'; -export * from './mockHelpers'; diff --git a/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx b/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx index 2aa6f6510db..710f90dc13b 100644 --- a/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx +++ b/public/app/features/plugins/admin/components/ConnectionsTab.test.tsx @@ -4,10 +4,10 @@ import { render } from 'test/test-utils'; import { DataSourceSettings } from '@grafana/data'; import { config } from '@grafana/runtime'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; -import { getMockDataSources } from 'app/features/datasources/__mocks__'; +import { getMockDataSources } from 'app/features/datasources/mocks/dataSourcesMocks'; import { AccessControlAction } from 'app/types'; -import { datasourcePlugin } from '../__mocks__/catalogPlugin.mock'; +import { datasourcePlugin } from '../mocks/catalogPlugin.mock'; import ConnectionsTab, { ConnectionsList } from './ConnectionsTab'; diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx index 44227cf3a24..dddca9112a3 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.test.tsx @@ -5,7 +5,7 @@ import { PluginSignatureStatus } from '@grafana/data'; import { config } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; -import { getPluginsStateMock } from '../../__mocks__'; +import { getPluginsStateMock } from '../../mocks/mockHelpers'; import { CatalogPlugin, PluginStatus } from '../../types'; import { InstallControlsButton } from './InstallControlsButton'; diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx index a24592c7d19..eb5e51d07b4 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.test.tsx @@ -5,7 +5,7 @@ import { PluginType } from '@grafana/data'; import { config } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; -import { getCatalogPluginMock } from '../__mocks__'; +import { getCatalogPluginMock } from '../mocks/mockHelpers'; import { PluginTabIds } from '../types'; import { PluginDetailsBody } from './PluginDetailsBody'; diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts index d59a1b80835..63dfb58a7a8 100644 --- a/public/app/features/plugins/admin/helpers.test.ts +++ b/public/app/features/plugins/admin/helpers.test.ts @@ -1,7 +1,6 @@ import { PluginErrorCode, PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { getLocalPluginMock, getRemotePluginMock, getCatalogPluginMock } from './__mocks__'; import { mapToCatalogPlugin, mapRemoteToCatalog, @@ -15,6 +14,7 @@ import { isNonAngularVersion, isDisabledAngularPlugin, } from './helpers'; +import { getLocalPluginMock, getRemotePluginMock, getCatalogPluginMock } from './mocks/mockHelpers'; import { RemotePlugin, LocalPlugin, RemotePluginStatus, Version, CatalogPlugin } from './types'; describe('Plugins/Helpers', () => { diff --git a/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts similarity index 100% rename from public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts rename to public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts diff --git a/public/app/features/plugins/admin/__mocks__/localPlugin.mock.ts b/public/app/features/plugins/admin/mocks/localPlugin.mock.ts similarity index 100% rename from public/app/features/plugins/admin/__mocks__/localPlugin.mock.ts rename to public/app/features/plugins/admin/mocks/localPlugin.mock.ts diff --git a/public/app/features/plugins/admin/__mocks__/mockHelpers.ts b/public/app/features/plugins/admin/mocks/mockHelpers.ts similarity index 100% rename from public/app/features/plugins/admin/__mocks__/mockHelpers.ts rename to public/app/features/plugins/admin/mocks/mockHelpers.ts diff --git a/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts b/public/app/features/plugins/admin/mocks/remotePlugin.mock.ts similarity index 100% rename from public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts rename to public/app/features/plugins/admin/mocks/remotePlugin.mock.ts diff --git a/public/app/features/plugins/admin/pages/Browse.test.tsx b/public/app/features/plugins/admin/pages/Browse.test.tsx index f270e4d0352..6b6d51a2bb1 100644 --- a/public/app/features/plugins/admin/pages/Browse.test.tsx +++ b/public/app/features/plugins/admin/pages/Browse.test.tsx @@ -5,7 +5,7 @@ import { PluginType, escapeStringForRegex } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; -import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__'; +import { getCatalogPluginMock, getPluginsStateMock } from '../mocks/mockHelpers'; import { fetchRemotePlugins } from '../state/actions'; import { CatalogPlugin, ReducerState, RequestStatus } from '../types'; diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index fa787881864..42b83c85835 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -14,9 +14,9 @@ import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; -import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock, mockUserPermissions } from '../__mocks__'; import * as api from '../api'; import { usePluginConfig } from '../hooks/usePluginConfig'; +import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock, mockUserPermissions } from '../mocks/mockHelpers'; import { fetchRemotePlugins } from '../state/actions'; import { CatalogPlugin, diff --git a/public/app/features/plugins/admin/state/selectors.test.ts b/public/app/features/plugins/admin/state/selectors.test.ts index 68d8ed23153..8749384e7a4 100644 --- a/public/app/features/plugins/admin/state/selectors.test.ts +++ b/public/app/features/plugins/admin/state/selectors.test.ts @@ -1,7 +1,7 @@ import { PluginType } from '@grafana/data'; import { configureStore } from 'app/store/configureStore'; -import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__'; +import { getCatalogPluginMock, getPluginsStateMock } from '../mocks/mockHelpers'; import { selectPlugins } from './selectors'; diff --git a/public/app/features/profile/UserProfileEditPage.test.tsx b/public/app/features/profile/UserProfileEditPage.test.tsx index 83152dee2e1..cc779f0e0a9 100644 --- a/public/app/features/profile/UserProfileEditPage.test.tsx +++ b/public/app/features/profile/UserProfileEditPage.test.tsx @@ -8,7 +8,7 @@ import { setPluginComponentsHook, usePluginComponents } from '@grafana/runtime'; import { backendSrv } from '../../core/services/backend_srv'; import { createComponentWithMeta } from '../plugins/extensions/usePluginComponents'; -import { getMockTeam } from '../teams/__mocks__/teamMocks'; +import { getMockTeam } from '../teams/mocks/teamMocks'; import { Props, UserProfileEditPage } from './UserProfileEditPage'; import { initialUserState } from './state/reducers'; diff --git a/public/app/features/profile/state/reducers.test.ts b/public/app/features/profile/state/reducers.test.ts index 352f824cc55..67ff64f9e56 100644 --- a/public/app/features/profile/state/reducers.test.ts +++ b/public/app/features/profile/state/reducers.test.ts @@ -1,6 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { OrgRole } from '../../../types'; -import { getMockTeam } from '../../teams/__mocks__/teamMocks'; +import { getMockTeam } from '../../teams/mocks/teamMocks'; import { initialUserState, diff --git a/public/app/features/query/components/QueryEditorRows.test.tsx b/public/app/features/query/components/QueryEditorRows.test.tsx index 8a05014c633..c165c783e2a 100644 --- a/public/app/features/query/components/QueryEditorRows.test.tsx +++ b/public/app/features/query/components/QueryEditorRows.test.tsx @@ -3,7 +3,7 @@ import { fireEvent, queryByLabelText, render, screen } from '@testing-library/re import { DataSourceRef, type DataQuery } from '@grafana/schema'; import { mockDataSource } from 'app/features/alerting/unified/mocks'; import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; -import createMockPanelData from 'app/plugins/datasource/azuremonitor/__mocks__/panelData'; +import createMockPanelData from 'app/plugins/datasource/azuremonitor/mocks/panelData'; import { QueryEditorRows, Props } from './QueryEditorRows'; diff --git a/public/app/features/query/state/__mocks__/mockDataSource.ts b/public/app/features/query/state/mocks/mockDataSource.ts similarity index 100% rename from public/app/features/query/state/__mocks__/mockDataSource.ts rename to public/app/features/query/state/mocks/mockDataSource.ts diff --git a/public/app/features/query/state/runRequest.test.ts b/public/app/features/query/state/runRequest.test.ts index f7620c9b965..57f49f152b5 100644 --- a/public/app/features/query/state/runRequest.test.ts +++ b/public/app/features/query/state/runRequest.test.ts @@ -19,7 +19,7 @@ import { deepFreeze } from '../../../../test/core/redux/reducerTester'; import { Echo } from '../../../core/services/echo/Echo'; import { createDashboardModelFixture } from '../../dashboard/state/__fixtures__/dashboardFixtures'; -import { getMockDataSource, TestQuery } from './__mocks__/mockDataSource'; +import { getMockDataSource, TestQuery } from './mocks/mockDataSource'; import { callQueryMethodWithMigration, runRequest } from './runRequest'; jest.mock('app/core/services/backend_srv'); diff --git a/public/app/features/teams/TeamGroupSync.test.tsx b/public/app/features/teams/TeamGroupSync.test.tsx index aee6a5a735d..841a85b5d4b 100644 --- a/public/app/features/teams/TeamGroupSync.test.tsx +++ b/public/app/features/teams/TeamGroupSync.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { TeamGroup } from '../../types'; import { Props, TeamGroupSync } from './TeamGroupSync'; -import { getMockTeamGroups } from './__mocks__/teamMocks'; +import { getMockTeamGroups } from './mocks/teamMocks'; const setup = (propOverrides?: object) => { const props: Props = { diff --git a/public/app/features/teams/TeamList.test.tsx b/public/app/features/teams/TeamList.test.tsx index 7629076e3c2..4826c2bc24f 100644 --- a/public/app/features/teams/TeamList.test.tsx +++ b/public/app/features/teams/TeamList.test.tsx @@ -7,7 +7,7 @@ import { contextSrv } from 'app/core/services/context_srv'; import { Team } from '../../types'; import { Props, TeamList } from './TeamList'; -import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks'; +import { getMockTeam, getMultipleMockTeams } from './mocks/teamMocks'; jest.mock('app/core/core', () => ({ contextSrv: { diff --git a/public/app/features/teams/TeamPages.test.tsx b/public/app/features/teams/TeamPages.test.tsx index f2d5c4674a9..ae180bd5e63 100644 --- a/public/app/features/teams/TeamPages.test.tsx +++ b/public/app/features/teams/TeamPages.test.tsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom-v5-compat'; import { render } from 'test/test-utils'; import TeamPages from './TeamPages'; -import { getMockTeam } from './__mocks__/teamMocks'; +import { getMockTeam } from './mocks/teamMocks'; jest.mock('app/core/components/Select/UserPicker', () => { return { UserPicker: () => null }; diff --git a/public/app/features/teams/TeamSettings.test.tsx b/public/app/features/teams/TeamSettings.test.tsx index 5b65cbfa800..a203c66eed9 100644 --- a/public/app/features/teams/TeamSettings.test.tsx +++ b/public/app/features/teams/TeamSettings.test.tsx @@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Props, TeamSettings } from './TeamSettings'; -import { getMockTeam } from './__mocks__/teamMocks'; +import { getMockTeam } from './mocks/teamMocks'; jest.mock('app/core/services/context_srv', () => ({ contextSrv: { diff --git a/public/app/features/teams/__mocks__/navModelMock.ts b/public/app/features/teams/mocks/navModelMock.ts similarity index 100% rename from public/app/features/teams/__mocks__/navModelMock.ts rename to public/app/features/teams/mocks/navModelMock.ts diff --git a/public/app/features/teams/__mocks__/teamMocks.ts b/public/app/features/teams/mocks/teamMocks.ts similarity index 100% rename from public/app/features/teams/__mocks__/teamMocks.ts rename to public/app/features/teams/mocks/teamMocks.ts diff --git a/public/app/features/teams/state/reducers.test.ts b/public/app/features/teams/state/reducers.test.ts index ba4984bd7be..483b22700f0 100644 --- a/public/app/features/teams/state/reducers.test.ts +++ b/public/app/features/teams/state/reducers.test.ts @@ -1,6 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { TeamsState, TeamState } from '../../../types'; -import { getMockTeam, getMockTeamGroups, getMockTeamMember } from '../__mocks__/teamMocks'; +import { getMockTeam, getMockTeamGroups, getMockTeamMember } from '../mocks/teamMocks'; import { initialTeamsState, diff --git a/public/app/features/teams/state/selectors.test.ts b/public/app/features/teams/state/selectors.test.ts index 644dad1c867..58f4dbbeeef 100644 --- a/public/app/features/teams/state/selectors.test.ts +++ b/public/app/features/teams/state/selectors.test.ts @@ -1,5 +1,5 @@ import { TeamState } from '../../../types'; -import { getMockTeam } from '../__mocks__/teamMocks'; +import { getMockTeam } from '../mocks/teamMocks'; import { getTeam } from './selectors'; diff --git a/public/app/features/users/__mocks__/userMocks.ts b/public/app/features/users/mocks/userMocks.ts similarity index 100% rename from public/app/features/users/__mocks__/userMocks.ts rename to public/app/features/users/mocks/userMocks.ts diff --git a/public/app/features/users/state/reducers.test.ts b/public/app/features/users/state/reducers.test.ts index e1759deb9b8..5c76c88ba5e 100644 --- a/public/app/features/users/state/reducers.test.ts +++ b/public/app/features/users/state/reducers.test.ts @@ -1,6 +1,6 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { UsersState } from '../../../types'; -import { getMockUsers, getFetchUsersMock } from '../__mocks__/userMocks'; +import { getMockUsers, getFetchUsersMock } from '../mocks/userMocks'; import { initialState, searchQueryChanged, usersLoaded, usersReducer } from './reducers'; diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts index e7fbc8c9115..2c59c4a38bb 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts @@ -1,12 +1,12 @@ import { CustomVariableModel } from '@grafana/data'; -import { Context, createContext } from '../__mocks__/datasource'; -import createMockQuery from '../__mocks__/query'; -import { singleVariable } from '../__mocks__/variables'; +import { Context, createContext } from '../mocks/datasource'; +import createMockQuery from '../mocks/query'; +import { singleVariable } from '../mocks/variables'; import { AzureLogsQuery, AzureMonitorQuery, AzureQueryType, AzureTracesQuery } from '../types'; -import FakeSchemaData from './__mocks__/schema'; import AzureLogAnalyticsDatasource from './azure_log_analytics_datasource'; +import FakeSchemaData from './mocks/schema'; let getTempVars = () => [] as CustomVariableModel[]; let replace = () => ''; diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/mocks/schema.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts rename to public/app/plugins/datasource/azuremonitor/azure_log_analytics/mocks/schema.ts diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts index d20428269dd..c5561e53be1 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts @@ -3,10 +3,10 @@ import { get, set } from 'lodash'; import { ScopedVars } from '@grafana/data'; import { VariableInterpolation } from '@grafana/runtime'; -import createMockQuery from '../__mocks__/query'; -import { createTemplateVariables } from '../__mocks__/utils'; -import { multiVariable } from '../__mocks__/variables'; import AzureMonitorDatasource from '../datasource'; +import createMockQuery from '../mocks/query'; +import { createTemplateVariables } from '../mocks/utils'; +import { multiVariable } from '../mocks/variables'; import { AzureAPIResponse, AzureMonitorDataSourceInstanceSettings, diff --git a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts index d5e1818f193..fc58c626fbf 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts @@ -2,11 +2,11 @@ import { set, get } from 'lodash'; import { CustomVariableModel } from '@grafana/data'; -import { Context, createContext } from '../__mocks__/datasource'; -import { createMockInstanceSetttings } from '../__mocks__/instanceSettings'; -import createMockQuery from '../__mocks__/query'; -import { createTemplateVariables } from '../__mocks__/utils'; -import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables'; +import { Context, createContext } from '../mocks/datasource'; +import { createMockInstanceSetttings } from '../mocks/instanceSettings'; +import createMockQuery from '../mocks/query'; +import { createTemplateVariables } from '../mocks/utils'; +import { multiVariable, singleVariable, subscriptionsVariable } from '../mocks/variables'; import { AzureQueryType } from '../types'; import AzureResourceGraphDatasource from './azure_resource_graph_datasource'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx index f01de772f6a..8da64b6024d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ArgQueryEditor/ArgQueryEditor.test.tsx @@ -1,10 +1,10 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; import { ARGScope } from '../../dataquery.gen'; import { selectors } from '../../e2e/selectors'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import ArgQueryEditor from './ArgQueryEditor'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.test.tsx index 69d28318934..2b4925435de 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/BasicLogsToggle.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react'; -import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; +import { createMockInstanceSetttings } from '../../mocks/instanceSettings'; import { BasicLogsToggle, Props } from './BasicLogsToggle'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.test.tsx index b10c96daa47..34f7a6345a1 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/DefaultSubscription.test.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; import { selectors } from '../../e2e/selectors'; +import { createMockInstanceSetttings } from '../../mocks/instanceSettings'; import { DefaultSubscription, Props } from './DefaultSubscription'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx index e7d2331d954..a098af09773 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor/MonitorConfig.test.tsx @@ -2,7 +2,7 @@ import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/re import { config } from '@grafana/runtime'; -import { createMockDatasourceSettings } from '../../__mocks__/datasourceSettings'; +import { createMockDatasourceSettings } from '../../mocks/datasourceSettings'; import { MonitorConfig, Props } from './MonitorConfig'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.test.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.test.tsx index 0f81976cb63..7343aea55ee 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsManagement.test.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { LogsManagement } from './LogsManagement'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.test.tsx index 106a7e4e66a..50b019d8aa0 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.test.tsx @@ -3,9 +3,9 @@ import userEvent from '@testing-library/user-event'; import { dateTime, LoadingState } from '@grafana/data'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; import { ResultFormat } from '../../dataquery.gen'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import LogsQueryEditor from './LogsQueryEditor'; import { createMockResourcePickerData } from './mocks'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.test.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.test.tsx index 5ba125daa21..9d9f2359dea 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/TimeManagement.test.tsx @@ -1,9 +1,9 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; -import FakeSchemaData from '../../azure_log_analytics/__mocks__/schema'; +import FakeSchemaData from '../../azure_log_analytics/mocks/schema'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { TimeManagement } from './TimeManagement'; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/mocks.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/mocks.ts index ecd27888afa..1690b7866eb 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/mocks.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/mocks.ts @@ -1,10 +1,10 @@ -import createMockDatasource from '../../__mocks__/datasource'; -import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; +import createMockDatasource from '../../mocks/datasource'; +import { createMockInstanceSetttings } from '../../mocks/instanceSettings'; import { createMockSubscriptions, createMockResourceGroupsBySubscription, mockResourcesByResourceGroup, -} from '../../__mocks__/resourcePickerRows'; +} from '../../mocks/resourcePickerRows'; import ResourcePickerData from '../../resourcePicker/resourcePickerData'; export function createMockResourcePickerData() { diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx index 13a003a20a0..35063802773 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/AggregationField.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { AzureMonitorOption } from '../../types'; import AggregationField from './AggregationField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.test.tsx index 26f7ccd755c..de60ae890b0 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/DimensionFields.test.tsx @@ -4,9 +4,9 @@ import { openMenu } from 'react-select-event'; import { selectors } from '@grafana/e2e-selectors'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockPanelData from '../../__mocks__/panelData'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockPanelData from '../../mocks/panelData'; +import createMockQuery from '../../mocks/query'; import { selectOptionInTest } from '../../utils/testUtils'; import DimensionFields from './DimensionFields'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx index 72813a642b9..06a08b1cdeb 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNameField.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { AzureMonitorOption } from '../../types'; import MetricNameField from './MetricNameField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx index 6fd05b9979c..dc1aab8b0bf 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricNamespaceField.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { AzureMonitorOption } from '../../types'; import MetricNamespaceField from './MetricNamespaceField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx index cf92f83b896..c8a4ed81048 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -1,10 +1,10 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockPanelData from '../../__mocks__/panelData'; -import createMockQuery from '../../__mocks__/query'; import { selectors } from '../../e2e/selectors'; +import createMockDatasource from '../../mocks/datasource'; +import createMockPanelData from '../../mocks/panelData'; +import createMockQuery from '../../mocks/query'; import { selectOptionInTest } from '../../utils/testUtils'; import { createMockResourcePickerData } from '../LogsQueryEditor/mocks'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx index a619620d06d..8722f5eee2c 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/TimeGrainField.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { AzureMonitorOption } from '../../types'; import TimeGrainField from './TimeGrainField'; diff --git a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts index 27e011f4311..acf197d2e2c 100644 --- a/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts +++ b/public/app/plugins/datasource/azuremonitor/components/MetricsQueryEditor/dataHooks.test.ts @@ -1,7 +1,7 @@ import { renderHook, waitFor } from '@testing-library/react'; -import createMockDatasource from '../../__mocks__/datasource'; import Datasource from '../../datasource'; +import createMockDatasource from '../../mocks/datasource'; import { AzureMetricQuery, AzureMonitorOption, AzureMonitorQuery, AzureQueryType } from '../../types'; import { diff --git a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx index 202e27e604b..5a944e472c3 100644 --- a/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.test.tsx @@ -4,10 +4,10 @@ import { CoreApp } from '@grafana/data'; 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 { selectors } from '../../e2e/selectors'; +import createMockDatasource from '../../mocks/datasource'; +import { invalidNamespaceError } from '../../mocks/errors'; +import createMockQuery from '../../mocks/query'; import { AzureQueryType, ResultFormat } from '../../types'; import { selectOptionInTest } from '../../utils/testUtils'; import { createMockResourcePickerData } from '../LogsQueryEditor/mocks'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.test.tsx b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.test.tsx index 69ada6687fa..4ef6385cbcf 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/ResourcePicker.test.tsx @@ -2,16 +2,16 @@ import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { omit } from 'lodash'; -import createMockDatasource from '../../__mocks__/datasource'; -import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; +import Datasource from '../../datasource'; +import createMockDatasource from '../../mocks/datasource'; +import { createMockInstanceSetttings } from '../../mocks/instanceSettings'; import { createMockResourceGroupsBySubscription, createMockSubscriptions, mockResourcesByResourceGroup, mockSearchResults, -} from '../../__mocks__/resourcePickerRows'; -import { DeepPartial } from '../../__mocks__/utils'; -import Datasource from '../../datasource'; +} from '../../mocks/resourcePickerRows'; +import { DeepPartial } from '../../mocks/utils'; import ResourcePickerData, { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData'; import { ResourceRowType } from './types'; diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.test.ts b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.test.ts index 119e9625bba..b876eff4b5d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.test.ts +++ b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/utils.test.ts @@ -1,4 +1,4 @@ -import createMockQuery from '../../__mocks__/query'; +import createMockQuery from '../../mocks/query'; import { ResourceRowGroup, ResourceRowType } from './types'; import { diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx index 9ae9327dae4..9eef48a3a4d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/Filters.test.tsx @@ -5,10 +5,10 @@ import { of } from 'rxjs'; import { CoreApp } from '@grafana/data'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; import { AzureQueryType } from '../../dataquery.gen'; import Datasource from '../../datasource'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { AzureMonitorQuery } from '../../types'; import { selectOptionInTest } from '../../utils/testUtils'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.test.tsx index 07f7d50f343..deef4b00bab 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TraceTypeField.test.tsx @@ -1,9 +1,9 @@ import { render, screen, act } from '@testing-library/react'; import { openMenu } from 'react-select-event'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; import { selectors } from '../../e2e/selectors'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import TraceTypeField from './TraceTypeField'; import { Tables } from './consts'; diff --git a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.test.tsx index 09f390aaa37..c0e4610c88b 100644 --- a/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/TracesQueryEditor/TracesQueryEditor.test.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { createMockResourcePickerData } from '../LogsQueryEditor/mocks'; import TracesQueryEditor from './TracesQueryEditor'; diff --git a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx index 22efae1e624..9f6c8ac8e75 100644 --- a/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/VariableEditor/VariableEditor.test.tsx @@ -5,7 +5,7 @@ import { select, openMenu } from 'react-select-event'; import * as ui from '@grafana/ui'; -import createMockDatasource from '../../__mocks__/datasource'; +import createMockDatasource from '../../mocks/datasource'; import { AzureMonitorQuery, AzureQueryType } from '../../types'; import VariableEditor from './VariableEditor'; diff --git a/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx b/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx index 4a31f801fee..fe06dc7cee1 100644 --- a/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/shared/FormatAsField.test.tsx @@ -2,8 +2,8 @@ import { render, screen } from '@testing-library/react'; import { SelectableValue } from '@grafana/data'; -import createMockDatasource from '../../__mocks__/datasource'; -import createMockQuery from '../../__mocks__/query'; +import createMockDatasource from '../../mocks/datasource'; +import createMockQuery from '../../mocks/query'; import { AzureMonitorQuery, ResultFormat } from '../../types'; import FormatAsField from './FormatAsField'; diff --git a/public/app/plugins/datasource/azuremonitor/dashboards/cosmosdb.json b/public/app/plugins/datasource/azuremonitor/dashboards/cosmosdb.json index b68ff4bb3f1..12887a253aa 100644 --- a/public/app/plugins/datasource/azuremonitor/dashboards/cosmosdb.json +++ b/public/app/plugins/datasource/azuremonitor/dashboards/cosmosdb.json @@ -1,22 +1,32 @@ { + "__inputs": [ + { + "name": "datasource", + "description": "", + "type": "datasource", + "pluginId": "grafana-azure-monitor-datasource", + "pluginName": "Azure Monitor" + } + ], + "__elements": {}, "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "7.4.3" + "version": "12.1.0-pre" }, { "type": "datasource", "id": "grafana-azure-monitor-datasource", "name": "Azure Monitor", - "version": "0.3.0" + "version": "12.1.0-pre" }, { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "" + "type": "datasource", + "id": "grafana-testdata-datasource", + "name": "TestData", + "version": "12.1.0-pre" }, { "type": "panel", @@ -31,16 +41,33 @@ "version": "" } ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, - "iteration": 1621378610748, "links": [], "panels": [ { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -54,7 +81,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "color": {}, @@ -190,6 +219,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -200,9 +233,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Total Requests", "tooltip": { "shared": true, @@ -211,33 +242,25 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, "min": "0", "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -245,7 +268,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "color": {}, @@ -388,6 +413,10 @@ ], "top": "" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -398,9 +427,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Throttled Requests (429s)", "tooltip": { "shared": true, @@ -409,33 +436,25 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, "min": "0", "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -443,7 +462,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "color": {}, @@ -556,6 +577,10 @@ ], "top": "" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -566,9 +591,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Normalized RU Consumption (max)", "tooltip": { "shared": true, @@ -577,33 +600,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "percent", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -611,7 +625,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "color": {}, @@ -708,6 +724,10 @@ ], "top": "" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -766,6 +786,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -777,9 +800,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Index & Data Usage", "tooltip": { "shared": true, @@ -788,44 +809,37 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "decbytes", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { "mode": "fixed" }, "custom": { - "align": null, "filterable": false }, "mappings": [], @@ -833,8 +847,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -847,8 +860,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "lcd-gauge" + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } }, { "id": "color", @@ -968,6 +984,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -978,8 +997,6 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Total Requests (Count) By Collection", "transformations": [ { @@ -992,14 +1009,16 @@ "type": "table" }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { "mode": "fixed" }, "custom": { - "align": null, "filterable": false }, "mappings": [], @@ -1007,8 +1026,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1021,8 +1039,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "lcd-gauge" + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } }, { "id": "color", @@ -1102,6 +1123,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -1112,8 +1136,6 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Document Count (Avg) By Collection", "transformations": [ { @@ -1126,14 +1148,16 @@ "type": "table" }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { "mode": "fixed" }, "custom": { - "align": null, "filterable": false }, "mappings": [], @@ -1141,8 +1165,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1155,8 +1178,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "lcd-gauge" + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } }, { "id": "color", @@ -1236,6 +1262,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -1246,8 +1275,6 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Data Usage (Avg) By Collection", "transformations": [ { @@ -1260,14 +1287,16 @@ "type": "table" }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { "mode": "fixed" }, "custom": { - "align": null, "filterable": false }, "mappings": [], @@ -1275,8 +1304,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1289,8 +1317,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "lcd-gauge" + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } }, { "id": "color", @@ -1370,6 +1401,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -1380,8 +1414,6 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Index Usage (Avg) By Collection", "transformations": [ { @@ -1394,14 +1426,16 @@ "type": "table" }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { "mode": "fixed" }, "custom": { - "align": null, "filterable": false }, "mappings": [], @@ -1409,8 +1443,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1423,8 +1456,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "lcd-gauge" + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } }, { "id": "color", @@ -1500,6 +1536,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -1510,8 +1549,6 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Provisioned Throughput (Max) By Collection", "transformations": [ { @@ -1524,14 +1561,16 @@ "type": "table" }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { "mode": "fixed" }, "custom": { - "align": null, "filterable": false }, "mappings": [], @@ -1539,8 +1578,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -1553,8 +1591,11 @@ }, "properties": [ { - "id": "custom.displayMode", - "value": "lcd-gauge" + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } }, { "id": "color", @@ -1650,6 +1691,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -1660,8 +1704,6 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Normalized RU Consumption (Max) By Collection", "transformations": [ { @@ -1674,12 +1716,22 @@ "type": "table" } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Overview", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -1693,7 +1745,10 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "custom": {} @@ -1824,6 +1879,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -1834,9 +1892,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Total Request Units", "tooltip": { "shared": true, @@ -1845,33 +1901,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -1879,7 +1926,10 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "custom": {} @@ -1994,6 +2044,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -2004,9 +2057,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Normalized RU Consumption By PartitionKeyRangeID", "tooltip": { "shared": true, @@ -2015,37 +2066,31 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "percent", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "color": { @@ -2133,6 +2178,9 @@ ], "top": "" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -2142,18 +2190,26 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Provisioned Throughput (Max) by Collection", "type": "stat" } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Throughput", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -2167,7 +2223,10 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "custom": {} @@ -2304,6 +2363,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -2314,9 +2376,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Total Requests by Status Code", "tooltip": { "shared": true, @@ -2325,33 +2385,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2359,7 +2410,10 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "custom": {} @@ -2496,6 +2550,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -2506,9 +2563,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Throttled Requests (429)", "tooltip": { "shared": true, @@ -2517,33 +2572,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2551,7 +2597,10 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "custom": {} @@ -2688,6 +2737,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -2698,9 +2750,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Total Requests by Operation Type", "tooltip": { "shared": true, @@ -2709,42 +2759,43 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Requests", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -2758,7 +2809,10 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "fieldConfig": { "defaults": { "custom": {} @@ -2818,7 +2872,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -2855,6 +2908,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -2881,7 +2937,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -2918,6 +2973,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -2929,9 +2988,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Data & Index Usage", "tooltip": { "shared": true, @@ -2940,33 +2997,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "decbytes", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2974,7 +3022,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -3034,7 +3084,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -3071,6 +3120,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -3081,9 +3134,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Document Count", "tooltip": { "shared": true, @@ -3092,37 +3143,30 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "color": { @@ -3134,8 +3178,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3223,6 +3266,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -3286,6 +3333,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -3350,6 +3400,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -3360,18 +3414,26 @@ "subscription": "$sub" } ], - "timeFrom": null, - "timeShift": null, "title": "Data, Index & Document Usage", "type": "stat" } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Storage", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -3385,7 +3447,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -3422,13 +3486,6 @@ "pointradius": 2, "points": false, "renderer": "flot", - "scopedVars": { - "sub": { - "selected": true, - "text": "RTD-Experimental - f7152080-b4e8-47ee-9c85-7f1d0e6b72dc", - "value": "f7152080-b4e8-47ee-9c85-7f1d0e6b72dc" - } - }, "seriesOverrides": [], "spaceLength": 10, "stack": false, @@ -3452,7 +3509,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -3476,6 +3532,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -3502,7 +3562,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -3526,6 +3585,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -3553,7 +3615,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -3577,6 +3638,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -3588,9 +3653,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Service Availability (min/max/avg in %)", "tooltip": { "shared": true, @@ -3599,43 +3662,44 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "percent", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], "repeat": "sub", + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Availability", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -3649,7 +3713,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -3758,6 +3824,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -3768,9 +3838,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Server Side Latency (Avg) By Region", "tooltip": { "shared": true, @@ -3779,33 +3847,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3813,7 +3872,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -3922,6 +3983,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -3932,9 +3997,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Server Side Latency (Avg) By Operation", "tooltip": { "shared": true, @@ -3943,42 +4006,43 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Latency", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -3992,7 +4056,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -4121,6 +4187,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -4131,9 +4201,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Metadata Requests by Status Code", "tooltip": { "shared": true, @@ -4142,33 +4210,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -4176,7 +4235,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -4305,6 +4366,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -4315,9 +4380,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Metadata Requests That Exceeded Capacity (429s)", "tooltip": { "shared": true, @@ -4326,42 +4389,43 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "System", "type": "row" }, { "collapsed": true, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "gridPos": { "h": 1, "w": 24, @@ -4375,7 +4439,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -4435,7 +4501,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -4459,6 +4524,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -4485,7 +4554,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -4509,6 +4577,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -4536,7 +4607,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -4565,6 +4635,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -4576,9 +4650,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Cosmos DB Account Management (Creates, Deletes) and Account Key Updates", "tooltip": { "shared": true, @@ -4587,33 +4659,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -4621,7 +4684,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "fieldConfig": { "defaults": { "custom": {} @@ -4681,7 +4746,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -4714,6 +4778,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "insightsAnalytics": { "query": "", "resultFormat": "time_series" @@ -4740,7 +4808,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -4764,6 +4831,9 @@ ], "top": "10" }, + "datasource": { + "uid": "$ds" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -4791,7 +4861,6 @@ "dimensionFilter": "*", "dimensionFilters": [ { - "dimension": null, "filter": "", "operator": "eq" } @@ -4815,6 +4884,10 @@ ], "top": "10" }, + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "hide": false, "insightsAnalytics": { "query": "", @@ -4826,9 +4899,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Cosmos DB Account Diagnostic, Network and Replication Settings Updates", "tooltip": { "shared": true, @@ -4837,49 +4908,46 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], + "targets": [ + { + "datasource": { + "uid": "$ds" + }, + "refId": "A" + } + ], "title": "Account Management", "type": "row" } ], - "refresh": false, - "schemaVersion": 27, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [ { "current": {}, - "description": null, - "error": null, "hide": 0, "includeAll": false, "label": "Data Source", @@ -4894,12 +4962,11 @@ "type": "datasource" }, { - "allValue": null, "current": {}, - "datasource": "$ds", + "datasource": { + "uid": "$ds" + }, "definition": "subscriptions()", - "description": null, - "error": null, "hide": 0, "includeAll": false, "label": "Subscription", @@ -4912,18 +4979,17 @@ "skipUrlSync": false, "sort": 5, "tagValuesQuery": "", - "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { - "allValue": null, "current": {}, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "definition": "", - "description": null, - "error": null, "hide": 0, "includeAll": false, "label": "Resource Group", @@ -4943,7 +5009,6 @@ "skipUrlSync": false, "sort": 5, "tagValuesQuery": "", - "tags": [], "tagsQuery": "", "type": "query", "useTags": false @@ -4976,15 +5041,14 @@ "type": "query" }, { - "allValue": null, "current": {}, - "datasource": "$ds", + "datasource": { + "type": "grafana-azure-monitor-datasource", + "uid": "${datasource}" + }, "definition": "", - "description": null, - "error": null, "hide": 0, "includeAll": false, - "label": null, "multi": false, "name": "resource", "options": [], @@ -5000,7 +5064,6 @@ "skipUrlSync": false, "sort": 5, "tagValuesQuery": "", - "tags": [], "tagsQuery": "", "type": "query", "useTags": false @@ -5015,5 +5078,6 @@ "timezone": "", "title": "Azure / Insights / Cosmos DB", "uid": "INH9berMk", - "version": 17 + "version": 2, + "weekStart": "" } diff --git a/public/app/plugins/datasource/azuremonitor/datasource.test.ts b/public/app/plugins/datasource/azuremonitor/datasource.test.ts index 7f41cc4b745..709e7c836d8 100644 --- a/public/app/plugins/datasource/azuremonitor/datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/datasource.test.ts @@ -1,7 +1,7 @@ -import { createMockInstanceSetttings } from './__mocks__/instanceSettings'; -import createMockQuery from './__mocks__/query'; -import { singleVariable } from './__mocks__/variables'; import Datasource from './datasource'; +import { createMockInstanceSetttings } from './mocks/instanceSettings'; +import createMockQuery from './mocks/query'; +import { singleVariable } from './mocks/variables'; import { AzureQueryType } from './types'; jest.mock('@grafana/runtime', () => { diff --git a/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts b/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts index 88b6dbd5fa3..68743a6942e 100644 --- a/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts +++ b/public/app/plugins/datasource/azuremonitor/grafanaTemplateVariables.test.ts @@ -1,5 +1,5 @@ -import createMockDatasource from './__mocks__/datasource'; import { migrateQuery, migrateStringQueriesToObjectQueries } from './grafanaTemplateVariableFns'; +import createMockDatasource from './mocks/datasource'; import { AzureMonitorQuery, AzureQueryType } from './types'; describe('migrateStringQueriesToObjectQueries', () => { diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/argResourcePickerResponse.ts b/public/app/plugins/datasource/azuremonitor/mocks/argResourcePickerResponse.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/argResourcePickerResponse.ts rename to public/app/plugins/datasource/azuremonitor/mocks/argResourcePickerResponse.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/datasource.ts b/public/app/plugins/datasource/azuremonitor/mocks/datasource.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/datasource.ts rename to public/app/plugins/datasource/azuremonitor/mocks/datasource.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/datasourceSettings.ts b/public/app/plugins/datasource/azuremonitor/mocks/datasourceSettings.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/datasourceSettings.ts rename to public/app/plugins/datasource/azuremonitor/mocks/datasourceSettings.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/errors.ts b/public/app/plugins/datasource/azuremonitor/mocks/errors.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/errors.ts rename to public/app/plugins/datasource/azuremonitor/mocks/errors.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/instanceSettings.ts b/public/app/plugins/datasource/azuremonitor/mocks/instanceSettings.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/instanceSettings.ts rename to public/app/plugins/datasource/azuremonitor/mocks/instanceSettings.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/panelData.ts b/public/app/plugins/datasource/azuremonitor/mocks/panelData.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/panelData.ts rename to public/app/plugins/datasource/azuremonitor/mocks/panelData.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/query.ts b/public/app/plugins/datasource/azuremonitor/mocks/query.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/query.ts rename to public/app/plugins/datasource/azuremonitor/mocks/query.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/resourcePickerRows.ts b/public/app/plugins/datasource/azuremonitor/mocks/resourcePickerRows.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/resourcePickerRows.ts rename to public/app/plugins/datasource/azuremonitor/mocks/resourcePickerRows.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/utils.ts b/public/app/plugins/datasource/azuremonitor/mocks/utils.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/utils.ts rename to public/app/plugins/datasource/azuremonitor/mocks/utils.ts diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/variables.ts b/public/app/plugins/datasource/azuremonitor/mocks/variables.ts similarity index 100% rename from public/app/plugins/datasource/azuremonitor/__mocks__/variables.ts rename to public/app/plugins/datasource/azuremonitor/mocks/variables.ts diff --git a/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts b/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts index 13988a15b4d..0243f717d52 100644 --- a/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts +++ b/public/app/plugins/datasource/azuremonitor/resourcePicker/resourcePickerData.test.ts @@ -1,13 +1,13 @@ +import { resourceTypes } from '../azureMetadata'; +import AzureResourceGraphDatasource from '../azure_resource_graph/azure_resource_graph_datasource'; +import { ResourceRowType } from '../components/ResourcePicker/types'; import { createARGResourcesResponse, createMockARGResourceGroupsResponse, createMockARGSubscriptionResponse, -} from '../__mocks__/argResourcePickerResponse'; -import createMockDatasource from '../__mocks__/datasource'; -import { createMockInstanceSetttings } from '../__mocks__/instanceSettings'; -import { resourceTypes } from '../azureMetadata'; -import AzureResourceGraphDatasource from '../azure_resource_graph/azure_resource_graph_datasource'; -import { ResourceRowType } from '../components/ResourcePicker/types'; +} from '../mocks/argResourcePickerResponse'; +import createMockDatasource from '../mocks/datasource'; +import { createMockInstanceSetttings } from '../mocks/instanceSettings'; import { AzureGraphResponse } from '../types'; import ResourcePickerData from './resourcePickerData'; diff --git a/public/app/plugins/datasource/azuremonitor/utils/common.test.ts b/public/app/plugins/datasource/azuremonitor/utils/common.test.ts index 0c55057fbfb..99b3cdba33f 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/common.test.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/common.test.ts @@ -1,4 +1,4 @@ -import { initialCustomVariableModelState } from '../__mocks__/variables'; +import { initialCustomVariableModelState } from '../mocks/variables'; import { hasOption, interpolateVariable } from './common'; diff --git a/public/app/plugins/datasource/azuremonitor/utils/messageFromError.test.ts b/public/app/plugins/datasource/azuremonitor/utils/messageFromError.test.ts index 8af0b8c8e45..6df351a9677 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/messageFromError.test.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/messageFromError.test.ts @@ -1,4 +1,4 @@ -import { invalidNamespaceError } from '../__mocks__/errors'; +import { invalidNamespaceError } from '../mocks/errors'; import messageFromError from './messageFromError'; diff --git a/public/app/plugins/datasource/azuremonitor/variables.test.ts b/public/app/plugins/datasource/azuremonitor/variables.test.ts index 84d4babcaf0..dcec7ff8005 100644 --- a/public/app/plugins/datasource/azuremonitor/variables.test.ts +++ b/public/app/plugins/datasource/azuremonitor/variables.test.ts @@ -2,8 +2,8 @@ import { from, lastValueFrom } from 'rxjs'; import { DataQueryRequest, toDataFrame } from '@grafana/data'; -import createMockDatasource from './__mocks__/datasource'; -import { invalidSubscriptionError } from './__mocks__/errors'; +import createMockDatasource from './mocks/datasource'; +import { invalidSubscriptionError } from './mocks/errors'; import { AzureMonitorQuery, AzureQueryType } from './types'; import { VariableSupport } from './variables'; diff --git a/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts b/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts index 291e0ddff19..c187351f957 100644 --- a/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts @@ -1,7 +1,7 @@ import { AnnotationQuery } from '@grafana/data'; -import { createMockDatasource } from './__mocks__/cloudMonitoringDatasource'; import { CloudMonitoringAnnotationSupport } from './annotationSupport'; +import { createMockDatasource } from './mocks/cloudMonitoringDatasource'; import { AlignmentTypes, CloudMonitoringQuery, diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx index da990da45e9..96bd3f8d71f 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx @@ -4,9 +4,9 @@ import { openMenu } from 'react-select-event'; import { CustomVariableModel } from '@grafana/data'; -import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource'; -import { createMockMetricDescriptor } from '../__mocks__/cloudMonitoringMetricDescriptor'; -import { createMockTimeSeriesList } from '../__mocks__/cloudMonitoringQuery'; +import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; +import { createMockMetricDescriptor } from '../mocks/cloudMonitoringMetricDescriptor'; +import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; import { MetricKind, ValueTypes } from '../types/query'; import { Alignment } from './Alignment'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.test.tsx index 349f07d3dfd..da1505cbb0f 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.test.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource'; -import { createMockQuery } from '../__mocks__/cloudMonitoringQuery'; +import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; +import { createMockQuery } from '../mocks/cloudMonitoringQuery'; import { AnnotationQueryEditor } from './AnnotationQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.test.tsx index 7342a9b0388..83dbe35d124 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { openMenu, select } from 'react-select-event'; -import { createMockTimeSeriesList } from '../__mocks__/cloudMonitoringQuery'; +import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; import { MetricDescriptor } from '../types/types'; import { GroupBy, Props } from './GroupBy'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx index 187481ca913..12ed2dfd0aa 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx @@ -4,8 +4,8 @@ import { openMenu } from 'react-select-event'; import { getDefaultTimeRange } from '@grafana/data'; -import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource'; -import { createMockQuery } from '../__mocks__/cloudMonitoringQuery'; +import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; +import { createMockQuery } from '../mocks/cloudMonitoringQuery'; import { QueryType } from '../types/query'; import { MetricQueryEditor } from './MetricQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx index aa6b89d4228..cb3003e9117 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx @@ -3,8 +3,8 @@ import userEvent from '@testing-library/user-event'; import { CustomVariableModel } from '@grafana/data'; -import { createMockMetricDescriptor } from '../__mocks__/cloudMonitoringMetricDescriptor'; -import { createMockTimeSeriesList } from '../__mocks__/cloudMonitoringQuery'; +import { createMockMetricDescriptor } from '../mocks/cloudMonitoringMetricDescriptor'; +import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; import { MetricKind, ValueTypes } from '../types/query'; import { Preprocessor } from './Preprocessor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx index cd3bb5d9668..7f410a17e8c 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx @@ -1,9 +1,9 @@ import { render, waitFor, screen } from '@testing-library/react'; import { select } from 'react-select-event'; -import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource'; -import { createMockQuery } from '../__mocks__/cloudMonitoringQuery'; import { selectors } from '../e2e/selectors'; +import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; +import { createMockQuery } from '../mocks/cloudMonitoringQuery'; import { QueryType } from '../types/query'; import { QueryEditor } from './QueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx index 3dfbe2c2bab..331ad7c4d4b 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import { openMenu, select } from 'react-select-event'; -import { createMockQuery } from '../__mocks__/cloudMonitoringQuery'; +import { createMockQuery } from '../mocks/cloudMonitoringQuery'; import { QueryType } from '../types/query'; import { QueryHeader } from './QueryHeader'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx index cfe82378a18..05e2f9c3721 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx @@ -6,9 +6,9 @@ import { CustomVariableModel, getDefaultTimeRange } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getTemplateSrv } from '@grafana/runtime'; -import { createMockDatasource } from '../__mocks__/cloudMonitoringDatasource'; -import { createMockMetricDescriptor } from '../__mocks__/cloudMonitoringMetricDescriptor'; -import { createMockTimeSeriesList } from '../__mocks__/cloudMonitoringQuery'; +import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; +import { createMockMetricDescriptor } from '../mocks/cloudMonitoringMetricDescriptor'; +import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; import { PreprocessorType, MetricKind, ValueTypes } from '../types/query'; import { defaultTimeSeriesList } from './MetricQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts b/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts index 59cd8cbdd0b..a87d4e266d0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts @@ -4,9 +4,9 @@ import { lastValueFrom, of } from 'rxjs'; import { CustomVariableModel, ScopedVars } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; -import { createMockInstanceSetttings } from './__mocks__/cloudMonitoringInstanceSettings'; -import { createMockQuery } from './__mocks__/cloudMonitoringQuery'; import Datasource from './datasource'; +import { createMockInstanceSetttings } from './mocks/cloudMonitoringInstanceSettings'; +import { createMockQuery } from './mocks/cloudMonitoringQuery'; import { CloudMonitoringQuery, PreprocessorType, QueryType, MetricKind } from './types/query'; let getTempVars = () => [] as CustomVariableModel[]; diff --git a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringDatasource.ts b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringDatasource.ts similarity index 100% rename from public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringDatasource.ts rename to public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringDatasource.ts diff --git a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringInstanceSettings.ts b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringInstanceSettings.ts similarity index 100% rename from public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringInstanceSettings.ts rename to public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringInstanceSettings.ts diff --git a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringMetricDescriptor.ts b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringMetricDescriptor.ts similarity index 100% rename from public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringMetricDescriptor.ts rename to public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringMetricDescriptor.ts diff --git a/public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringQuery.ts b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloud-monitoring/__mocks__/cloudMonitoringQuery.ts rename to public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx index e9bd97a82f9..9178eb849c4 100644 --- a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx @@ -3,8 +3,8 @@ import '@testing-library/jest-dom'; import { QueryEditorProps } from '@grafana/data'; -import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { CloudWatchDatasource } from '../../datasource'; +import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchMetricsQuery, CloudWatchQuery } from '../../types'; import { AnnotationQueryEditor } from './AnnotationQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx index b7ac8deadb9..b397f39f983 100644 --- a/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/ConfigEditor/ConfigEditor.test.tsx @@ -4,12 +4,12 @@ import userEvent from '@testing-library/user-event'; import { AwsAuthType } from '@grafana/aws-sdk'; import { PluginContextProvider, PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data'; +import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchSettings, setupMockedDataSource, setupMockedTemplateService, -} from '../../__mocks__/CloudWatchDataSource'; -import { CloudWatchDatasource } from '../../datasource'; +} from '../../mocks/CloudWatchDataSource'; import { ConfigEditor, diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx index b070fb69a0e..8c2ed9adb1f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx @@ -2,9 +2,9 @@ import { act, render, screen, waitFor } from '@testing-library/react'; import { LoadingState } from '@grafana/data'; -import { setupMockedDataSource } from '../../../__mocks__/CloudWatchDataSource'; -import { RequestMock } from '../../../__mocks__/Request'; -import { validLogsQuery } from '../../../__mocks__/queries'; +import { setupMockedDataSource } from '../../../mocks/CloudWatchDataSource'; +import { RequestMock } from '../../../mocks/Request'; +import { validLogsQuery } from '../../../mocks/queries'; import { CloudWatchLogsQuery } from '../../../types'; import { CloudWatchLink } from './CloudWatchLink'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx index 4b9d5cc4cd1..bb87c2c6d19 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -4,9 +4,9 @@ import selectEvent from 'react-select-event'; import { CustomVariableModel, DataSourceInstanceSettings } from '@grafana/data'; import * as ui from '@grafana/ui'; -import { setupMockedTemplateService } from '../../../__mocks__/CloudWatchDataSource'; -import { initialVariableModelState } from '../../../__mocks__/CloudWatchVariables'; import { CloudWatchDatasource } from '../../../datasource'; +import { setupMockedTemplateService } from '../../../mocks/CloudWatchDataSource'; +import { initialVariableModelState } from '../../../mocks/CloudWatchVariables'; import { CloudWatchJsonData, MetricEditorMode, MetricQueryType } from '../../../types'; import { MetricsQueryEditor, Props } from './MetricsQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx index 5060f6ff1a3..2e3c8e16403 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react'; -import { setupMockedDataSource } from '../../../../__mocks__/CloudWatchDataSource'; import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../../../expressions'; +import { setupMockedDataSource } from '../../../../mocks/CloudWatchDataSource'; import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../types'; import { SQLBuilderEditor } from './SQLBuilderEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx index 65b709bf72c..fca1c946f06 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx @@ -1,8 +1,8 @@ import { act, render, screen } from '@testing-library/react'; import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; -import { setupMockedDataSource } from '../../../../__mocks__/CloudWatchDataSource'; import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../../../expressions'; +import { setupMockedDataSource } from '../../../../mocks/CloudWatchDataSource'; import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../types'; import SQLBuilderSelectRow from './SQLBuilderSelectRow'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx index 6b670310a01..315eafb511a 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx @@ -4,8 +4,8 @@ import selectEvent from 'react-select-event'; import { config } from '@grafana/runtime'; -import { setupMockedDataSource } from '../../../../__mocks__/CloudWatchDataSource'; -import { createArray, createGroupBy } from '../../../../__mocks__/sqlUtils'; +import { setupMockedDataSource } from '../../../../mocks/CloudWatchDataSource'; +import { createArray, createGroupBy } from '../../../../mocks/sqlUtils'; import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../types'; import SQLGroupBy from './SQLGroupBy'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx index 8a78f9a6f0a..1d87bd29596 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx @@ -5,16 +5,16 @@ import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; import { QueryEditorProps } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; +import { CloudWatchDatasource } from '../../datasource'; +import { DEFAULT_CWLI_QUERY_STRING, DEFAULT_SQL_QUERY_STRING } from '../../defaultQueries'; +import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; import { validLogsQuery, validMetricQueryBuilderQuery, validMetricQueryCodeQuery, validMetricSearchBuilderQuery, validMetricSearchCodeQuery, -} from '../../__mocks__/queries'; -import { CloudWatchDatasource } from '../../datasource'; -import { DEFAULT_CWLI_QUERY_STRING, DEFAULT_SQL_QUERY_STRING } from '../../defaultQueries'; +} from '../../mocks/queries'; import { CloudWatchQuery, CloudWatchJsonData, MetricEditorMode, MetricQueryType, LogsQueryLanguage } from '../../types'; import { QueryEditor } from './QueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.test.tsx index ddf208ebb95..01af5b18121 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.test.tsx @@ -3,10 +3,10 @@ import selectEvent from 'react-select-event'; import { config } from '@grafana/runtime'; -import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; -import { validLogsQuery, validMetricSearchBuilderQuery } from '../../__mocks__/queries'; import { LogsQueryLanguage } from '../../dataquery.gen'; import { DEFAULT_CWLI_QUERY_STRING } from '../../defaultQueries'; +import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; +import { validLogsQuery, validMetricSearchBuilderQuery } from '../../mocks/queries'; import QueryHeader from './QueryHeader'; diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/MultiFilter.test.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/MultiFilter.test.tsx index b14051880fc..5cc5b0aafcb 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/MultiFilter.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/MultiFilter.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; +import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; import { MultiFilter } from './MultiFilter'; 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 be8cb585d4c..23bb9488d29 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx @@ -2,7 +2,7 @@ import { act, fireEvent, render, screen, waitFor, within } from '@testing-librar import userEvent from '@testing-library/user-event'; import { select } from 'react-select-event'; -import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; +import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; import { GetDimensionKeysRequest } from '../../resources/types'; import { VariableQueryType } from '../../types'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx index ee39afba589..6374c95b8a6 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { setupMockedDataSource } from '../../../__mocks__/CloudWatchDataSource'; +import { setupMockedDataSource } from '../../../mocks/CloudWatchDataSource'; import { CloudWatchMetricsQuery } from '../../../types'; import { Dimensions } from './Dimensions'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx index 9ea8123bd2b..2afbd64e195 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { setupMockedDataSource } from '../../../__mocks__/CloudWatchDataSource'; +import { setupMockedDataSource } from '../../../mocks/CloudWatchDataSource'; import { CloudWatchMetricsQuery } from '../../../types'; import { FilterItem } from './FilterItem'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.test.tsx index 6bf4154d9ac..4822dbc8cc7 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.test.tsx @@ -8,7 +8,7 @@ import { logGroupNamesVariable, setupMockedDataSource, setupMockedTemplateService, -} from '../../../__mocks__/CloudWatchDataSource'; +} from '../../../mocks/CloudWatchDataSource'; import { LogGroupsField } from './LogGroupsField'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx index a88b4f4fd61..5eec534a5ca 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx @@ -4,8 +4,8 @@ import selectEvent from 'react-select-event'; import { config } from '@grafana/runtime'; -import { setupMockedDataSource, statisticVariable } from '../../../__mocks__/CloudWatchDataSource'; -import { validMetricSearchBuilderQuery } from '../../../__mocks__/queries'; +import { setupMockedDataSource, statisticVariable } from '../../../mocks/CloudWatchDataSource'; +import { validMetricSearchBuilderQuery } from '../../../mocks/queries'; import { MetricStat } from '../../../types'; import { MetricStatEditor } from './MetricStatEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index 4d17c9b8eca..f70b15f9caa 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -9,10 +9,10 @@ import { logGroupNamesVariable, regionVariable, setupMockedDataSource, -} from './__mocks__/CloudWatchDataSource'; -import { setupForLogs } from './__mocks__/logsTestContext'; -import { validLogsQuery, validMetricSearchBuilderQuery } from './__mocks__/queries'; -import { TimeRangeMock } from './__mocks__/timeRange'; +} from './mocks/CloudWatchDataSource'; +import { setupForLogs } from './mocks/logsTestContext'; +import { validLogsQuery, validMetricSearchBuilderQuery } from './mocks/queries'; +import { TimeRangeMock } from './mocks/timeRange'; import { CloudWatchDefaultQuery, CloudWatchLogsQuery, diff --git a/public/app/plugins/datasource/cloudwatch/hooks.test.ts b/public/app/plugins/datasource/cloudwatch/hooks.test.ts index 326a56b4b6d..ebf88ecbf49 100644 --- a/public/app/plugins/datasource/cloudwatch/hooks.test.ts +++ b/public/app/plugins/datasource/cloudwatch/hooks.test.ts @@ -2,15 +2,6 @@ import { renderHook, waitFor } from '@testing-library/react'; import { config } from '@grafana/runtime'; -import { - accountIdVariable, - dimensionVariable, - metricVariable, - namespaceVariable, - regionVariable, - setupMockedDataSource, -} from './__mocks__/CloudWatchDataSource'; -import { setupMockedResourcesAPI } from './__mocks__/ResourcesAPI'; import { useAccountOptions, useDimensionKeys, @@ -18,6 +9,15 @@ import { useMetrics, useEnsureVariableHasSingleSelection, } from './hooks'; +import { + accountIdVariable, + dimensionVariable, + metricVariable, + namespaceVariable, + regionVariable, + setupMockedDataSource, +} from './mocks/CloudWatchDataSource'; +import { setupMockedResourcesAPI } from './mocks/ResourcesAPI'; const originalFeatureToggleValue = config.featureToggles.cloudWatchCrossAccountQuerying; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts index 6886d29d0cd..04b71f35a6d 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts @@ -1,14 +1,14 @@ import { CustomVariableModel } from '@grafana/data'; import { Monaco, monacoTypes } from '@grafana/ui'; -import { setupMockedTemplateService, logGroupNamesVariable } from '../../../__mocks__/CloudWatchDataSource'; -import { multiLineFullQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQuery'; -import { multiLineFullQueryWithCaseClause } from '../../../__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause'; -import { partialQueryWithSubquery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithSubquery'; -import { singleLineFullQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/singleLineFullQuery'; -import { whitespaceQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/whitespaceQuery'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +import { setupMockedTemplateService, logGroupNamesVariable } from '../../../mocks/CloudWatchDataSource'; +import { multiLineFullQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery'; +import { multiLineFullQueryWithCaseClause } from '../../../mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause'; +import { partialQueryWithSubquery } from '../../../mocks/cloudwatch-logs-sql-test-data/partialQueryWithSubquery'; +import { singleLineFullQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/singleLineFullQuery'; +import { whitespaceQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/whitespaceQuery'; +import MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import { ResourceResponse } from '../../../resources/types'; import { LogGroup, LogGroupField } from '../../../types'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/statementPosition.test.ts index 2b4deed6d77..ef5d2273a95 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/statementPosition.test.ts @@ -1,14 +1,14 @@ import { monacoTypes } from '@grafana/ui'; -import { commentOnlyQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/commentOnlyQuery'; -import { multiLineFullQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQuery'; -import { multiLineFullQueryWithCaseClause } from '../../../__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause'; -import { partialQueryWithFunction } from '../../../__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithFunction'; -import { partialQueryWithSubquery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithSubquery'; -import { singleLineFullQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/singleLineFullQuery'; -import { whitespaceQuery } from '../../../__mocks__/cloudwatch-logs-sql-test-data/whitespaceQuery'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +import { commentOnlyQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/commentOnlyQuery'; +import { multiLineFullQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery'; +import { multiLineFullQueryWithCaseClause } from '../../../mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause'; +import { partialQueryWithFunction } from '../../../mocks/cloudwatch-logs-sql-test-data/partialQueryWithFunction'; +import { partialQueryWithSubquery } from '../../../mocks/cloudwatch-logs-sql-test-data/partialQueryWithSubquery'; +import { singleLineFullQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/singleLineFullQuery'; +import { whitespaceQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/whitespaceQuery'; +import MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; import { StatementPosition } from '../../monarch/types'; import cloudWatchLogsSqlLanguageDefinition from '../definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.test.ts index 77a59d1fa21..1f475ec5fb8 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.test.ts @@ -3,8 +3,8 @@ import { Value } from 'slate'; import { TypeaheadOutput } from '@grafana/ui'; -import { setupMockedTemplateService } from '../../__mocks__/CloudWatchDataSource'; import { CloudWatchDatasource } from '../../datasource'; +import { setupMockedTemplateService } from '../../mocks/CloudWatchDataSource'; import { ResourceResponse } from '../../resources/types'; import { LogGroupField } from '../../types'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts index f1403d3682b..7809ac001b5 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts @@ -1,8 +1,8 @@ import { CustomVariableModel } from '@grafana/data'; import { Monaco, monacoTypes } from '@grafana/ui'; -import { logGroupNamesVariable, setupMockedTemplateService } from '../../../__mocks__/CloudWatchDataSource'; -import { newCommandQuery } from '../../../__mocks__/cloudwatch-ppl-test-data/newCommandQuery'; +import { logGroupNamesVariable, setupMockedTemplateService } from '../../../mocks/CloudWatchDataSource'; +import { newCommandQuery } from '../../../mocks/cloudwatch-ppl-test-data/newCommandQuery'; import { dedupQueryWithOptionalArgs, emptyQuery, @@ -17,9 +17,9 @@ import { statsQuery, topQuery, whereQuery, -} from '../../../__mocks__/cloudwatch-ppl-test-data/singleLineQueries'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +} from '../../../mocks/cloudwatch-ppl-test-data/singleLineQueries'; +import MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import { ResourceResponse } from '../../../resources/types'; import { LogGroup, LogGroupField } from '../../../types'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/statementPosition.test.ts index d127553c9f3..1e3bf9f0def 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/statementPosition.test.ts @@ -1,6 +1,6 @@ import { monacoTypes } from '@grafana/ui'; -import { multiLineFullQuery } from '../../../__mocks__/cloudwatch-ppl-test-data/multilineQueries'; +import { multiLineFullQuery } from '../../../mocks/cloudwatch-ppl-test-data/multilineQueries'; import { dedupQueryWithOptionalArgs, dedupQueryWithoutOptionalArgs, @@ -19,9 +19,9 @@ import { statsQuery, topQuery, whereQuery, -} from '../../../__mocks__/cloudwatch-ppl-test-data/singleLineQueries'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +} from '../../../mocks/cloudwatch-ppl-test-data/singleLineQueries'; +import MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; import { StatementPosition } from '../../monarch/types'; import cloudWatchLogsPPLLanguageDefinition from '../definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts index 3d44b277df2..83f943a4f95 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts @@ -1,10 +1,11 @@ +import { QueryEditorExpressionType } from '../../expressions'; import { aggregationvariable, labelsVariable, metricVariable, namespaceVariable, setupMockedTemplateService, -} from '../../__mocks__/CloudWatchDataSource'; +} from '../../mocks/CloudWatchDataSource'; import { createFunctionWithParameter, createArray, @@ -12,8 +13,7 @@ import { createGroupBy, createFunction, createProperty, -} from '../../__mocks__/sqlUtils'; -import { QueryEditorExpressionType } from '../../expressions'; +} from '../../mocks/sqlUtils'; import { SQLExpression } from '../../types'; import SQLGenerator from './SQLGenerator'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/statementPosition.test.ts index 5e271e2f445..8cfcb4c7960 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/statementPosition.test.ts @@ -5,9 +5,9 @@ import { singleLineFullQuery, singleLineEmptyQuery, singleLineTwoQueries, -} from '../../../__mocks__/cloudwatch-sql-test-data'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +} 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 cloudWatchSqlLanguageDefinition from '../definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/tokenUtils.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/tokenUtils.test.ts index b64b2b98440..54e44f44866 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/tokenUtils.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/completion/tokenUtils.test.ts @@ -5,9 +5,9 @@ import { singleLineFullQuery, singleLineTwoQueries, multiLineIncompleteQueryWithoutNamespace, -} from '../../../__mocks__/cloudwatch-sql-test-data'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +} 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 cloudWatchSqlLanguageDefinition from '../definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/dynamic-labels/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/dynamic-labels/CompletionItemProvider.test.ts index ae14f6c74bd..c91dad19e38 100644 --- a/public/app/plugins/datasource/cloudwatch/language/dynamic-labels/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/dynamic-labels/CompletionItemProvider.test.ts @@ -1,9 +1,9 @@ import { CompletionItemPriority } from '@grafana/plugin-ui'; import { Monaco, monacoTypes } from '@grafana/ui'; -import { afterLabelValue, insideLabelValue } from '../../__mocks__/dynamic-label-test-data'; -import MonacoMock from '../../__mocks__/monarch/Monaco'; -import TextModel from '../../__mocks__/monarch/TextModel'; +import { afterLabelValue, insideLabelValue } from '../../mocks/dynamic-label-test-data'; +import MonacoMock from '../../mocks/monarch/Monaco'; +import TextModel from '../../mocks/monarch/TextModel'; import { DynamicLabelsCompletionItemProvider } from './CompletionItemProvider'; import cloudWatchDynamicLabelsLanguageDefinition from './definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts index 63ddcc5e1cb..3a70d392749 100644 --- a/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts @@ -1,10 +1,10 @@ import { CustomVariableModel } from '@grafana/data'; import { Monaco, monacoTypes } from '@grafana/ui'; -import { setupMockedTemplateService, logGroupNamesVariable } from '../../../__mocks__/CloudWatchDataSource'; -import { emptyQuery, filterQuery, newCommandQuery, sortQuery } from '../../../__mocks__/cloudwatch-logs-test-data'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +import { setupMockedTemplateService, logGroupNamesVariable } from '../../../mocks/CloudWatchDataSource'; +import { emptyQuery, filterQuery, newCommandQuery, sortQuery } from '../../../mocks/cloudwatch-logs-test-data'; +import MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import { ResourceResponse } from '../../../resources/types'; import { LogGroup, LogGroupField } from '../../../types'; diff --git a/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts index c2ef700318f..0eac4cecc43 100644 --- a/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/logs/completion/statementPosition.test.ts @@ -6,9 +6,9 @@ import { commentOnlyQuery, singleLineFullQuery, multiLineFullQuery, -} from '../../../__mocks__/cloudwatch-logs-test-data'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +} from '../../../mocks/cloudwatch-logs-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 cloudWatchLogsLanguageDefinition from '../definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/CompletionItemProvider.test.ts index 887d3dd940d..2b5cf635293 100644 --- a/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/CompletionItemProvider.test.ts @@ -1,9 +1,9 @@ import { Monaco, monacoTypes } from '@grafana/ui'; -import { setupMockedTemplateService } from '../../../__mocks__/CloudWatchDataSource'; -import * as MetricMathTestData from '../../../__mocks__/metric-math-test-data'; -import MonacoMock from '../../../__mocks__/monarch/Monaco'; -import TextModel from '../../../__mocks__/monarch/TextModel'; +import { setupMockedTemplateService } from '../../../mocks/CloudWatchDataSource'; +import * as MetricMathTestData from '../../../mocks/metric-math-test-data'; +import MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import cloudWatchMetricMathLanguageDefinition from '../definition'; import { diff --git a/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/statementPosition.test.ts b/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/statementPosition.test.ts index c786dfb5281..7b8c5e41752 100644 --- a/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/statementPosition.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/metric-math/completion/statementPosition.test.ts @@ -1,8 +1,8 @@ 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 MonacoMock from '../../../mocks/monarch/Monaco'; +import TextModel from '../../../mocks/monarch/TextModel'; import { linkedTokenBuilder } from '../../monarch/linkedTokenBuilder'; import { StatementPosition } from '../../monarch/types'; import cloudWatchSqlLanguageDefinition from '../definition'; diff --git a/public/app/plugins/datasource/cloudwatch/language/monarch/linkedTokenBuilder.test.ts b/public/app/plugins/datasource/cloudwatch/language/monarch/linkedTokenBuilder.test.ts index 575e94fa615..391b286c522 100644 --- a/public/app/plugins/datasource/cloudwatch/language/monarch/linkedTokenBuilder.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/monarch/linkedTokenBuilder.test.ts @@ -1,8 +1,8 @@ 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 MonacoMock from '../../mocks/monarch/Monaco'; +import TextModel from '../../mocks/monarch/TextModel'; import { SQLTokenTypes } from '../cloudwatch-sql/completion/types'; import cloudWatchSqlLanguageDefinition from '../cloudwatch-sql/definition'; import { DESC, SELECT } from '../cloudwatch-sql/language'; diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/AnnotationQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/mocks/AnnotationQueryRunner.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/AnnotationQueryRunner.ts rename to public/app/plugins/datasource/cloudwatch/mocks/AnnotationQueryRunner.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts b/public/app/plugins/datasource/cloudwatch/mocks/CloudWatchDataSource.ts similarity index 99% rename from public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts rename to public/app/plugins/datasource/cloudwatch/mocks/CloudWatchDataSource.ts index 2e3d5a4401e..ddd4b19e15a 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts +++ b/public/app/plugins/datasource/cloudwatch/mocks/CloudWatchDataSource.ts @@ -11,11 +11,12 @@ import { } from '@grafana/data'; import { getBackendSrv, setBackendSrv, DataSourceWithBackend, TemplateSrv } from '@grafana/runtime'; -import { initialCustomVariableModelState } from '../__mocks__/CloudWatchVariables'; import { CloudWatchDatasource } from '../datasource'; import { CloudWatchJsonData } from '../types'; import { getVariableName } from '../utils/templateVariableUtils'; +import { initialCustomVariableModelState } from './CloudWatchVariables'; + const queryMock = jest.fn().mockReturnValue(of({ data: [] })); jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation((args) => queryMock(args)); const separatorMap = new Map([ diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchVariables.ts b/public/app/plugins/datasource/cloudwatch/mocks/CloudWatchVariables.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchVariables.ts rename to public/app/plugins/datasource/cloudwatch/mocks/CloudWatchVariables.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/LogsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/mocks/LogsQueryRunner.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/LogsQueryRunner.ts rename to public/app/plugins/datasource/cloudwatch/mocks/LogsQueryRunner.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/MetricsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/mocks/MetricsQueryRunner.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/MetricsQueryRunner.ts rename to public/app/plugins/datasource/cloudwatch/mocks/MetricsQueryRunner.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/Request.ts b/public/app/plugins/datasource/cloudwatch/mocks/Request.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/Request.ts rename to public/app/plugins/datasource/cloudwatch/mocks/Request.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/ResourcesAPI.ts b/public/app/plugins/datasource/cloudwatch/mocks/ResourcesAPI.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/ResourcesAPI.ts rename to public/app/plugins/datasource/cloudwatch/mocks/ResourcesAPI.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/commentOnlyQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/commentOnlyQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/commentOnlyQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/commentOnlyQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithFunction.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithFunction.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithFunction.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithFunction.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithSubquery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithSubquery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/partialQueryWithSubquery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/partialQueryWithSubquery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/singleLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/singleLineFullQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/singleLineFullQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/singleLineFullQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/whitespaceQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/whitespaceQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-sql-test-data/whitespaceQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-sql-test-data/whitespaceQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/commentOnlyQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/commentOnlyQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/commentOnlyQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/commentOnlyQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/empty.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/empty.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/empty.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/empty.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/filterQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/filterQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/filterQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/filterQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/index.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/index.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/index.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/multiLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/multiLineFullQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/multiLineFullQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/multiLineFullQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/newCommandQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/newCommandQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/newCommandQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/newCommandQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/singleLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/singleLineFullQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/singleLineFullQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/singleLineFullQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/sortQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/sortQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/sortQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/sortQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/whitespaceQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/whitespaceQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-logs-test-data/whitespaceQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-logs-test-data/whitespaceQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-ppl-test-data/multilineQueries.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/multilineQueries.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-ppl-test-data/multilineQueries.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/multilineQueries.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-ppl-test-data/newCommandQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/newCommandQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-ppl-test-data/newCommandQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/newCommandQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-ppl-test-data/singleLineQueries.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/singleLineQueries.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-ppl-test-data/singleLineQueries.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-ppl-test-data/singleLineQueries.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/index.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/index.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/index.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/multiLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineFullQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/multiLineFullQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineFullQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/multiLineIncompleteQueryWithoutNamespace.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineIncompleteQueryWithoutNamespace.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/multiLineIncompleteQueryWithoutNamespace.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/multiLineIncompleteQueryWithoutNamespace.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/singleLineEmptyQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineEmptyQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/singleLineEmptyQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineEmptyQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/singleLineFullQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineFullQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/singleLineFullQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineFullQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/singleLineTwoQueries.ts b/public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineTwoQueries.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/cloudwatch-sql-test-data/singleLineTwoQueries.ts rename to public/app/plugins/datasource/cloudwatch/mocks/cloudwatch-sql-test-data/singleLineTwoQueries.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/dashboardOnLoadedEvent.ts b/public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/dashboardOnLoadedEvent.ts rename to public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/afterLabelValue.ts b/public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/afterLabelValue.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/afterLabelValue.ts rename to public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/afterLabelValue.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/index.ts b/public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/index.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/index.ts rename to public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/index.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/insideLabelValue.ts b/public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/insideLabelValue.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/dynamic-label-test-data/insideLabelValue.ts rename to public/app/plugins/datasource/cloudwatch/mocks/dynamic-label-test-data/insideLabelValue.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/logsTestContext.ts b/public/app/plugins/datasource/cloudwatch/mocks/logsTestContext.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/logsTestContext.ts rename to public/app/plugins/datasource/cloudwatch/mocks/logsTestContext.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/afterFunctionQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/afterFunctionQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/afterFunctionQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/afterFunctionQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/index.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/index.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/index.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/secondArgAfterSearchQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgAfterSearchQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/secondArgAfterSearchQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgAfterSearchQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/secondArgQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/secondArgQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/secondArgQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/singleLineEmptyQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/singleLineEmptyQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/singleLineEmptyQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/singleLineEmptyQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/thirdArgAfterSearchQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/thirdArgAfterSearchQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/thirdArgAfterSearchQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/thirdArgAfterSearchQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/withinStringQuery.ts b/public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/withinStringQuery.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/metric-math-test-data/withinStringQuery.ts rename to public/app/plugins/datasource/cloudwatch/mocks/metric-math-test-data/withinStringQuery.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts b/public/app/plugins/datasource/cloudwatch/mocks/monarch/Monaco.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts rename to public/app/plugins/datasource/cloudwatch/mocks/monarch/Monaco.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/monarch/TextModel.ts b/public/app/plugins/datasource/cloudwatch/mocks/monarch/TextModel.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/monarch/TextModel.ts rename to public/app/plugins/datasource/cloudwatch/mocks/monarch/TextModel.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/queries.ts b/public/app/plugins/datasource/cloudwatch/mocks/queries.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/queries.ts rename to public/app/plugins/datasource/cloudwatch/mocks/queries.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/sqlUtils.ts b/public/app/plugins/datasource/cloudwatch/mocks/sqlUtils.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/sqlUtils.ts rename to public/app/plugins/datasource/cloudwatch/mocks/sqlUtils.ts diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/timeRange.ts b/public/app/plugins/datasource/cloudwatch/mocks/timeRange.ts similarity index 100% rename from public/app/plugins/datasource/cloudwatch/__mocks__/timeRange.ts rename to public/app/plugins/datasource/cloudwatch/mocks/timeRange.ts diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts index 75df7ff069e..cac628cea6e 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts @@ -1,5 +1,5 @@ -import { setupMockedAnnotationQueryRunner } from '../__mocks__/AnnotationQueryRunner'; -import { namespaceVariable, regionVariable } from '../__mocks__/CloudWatchDataSource'; +import { setupMockedAnnotationQueryRunner } from '../mocks/AnnotationQueryRunner'; +import { namespaceVariable, regionVariable } from '../mocks/CloudWatchDataSource'; import { CloudWatchAnnotationQuery } from '../types'; describe('CloudWatchAnnotationQueryRunner', () => { diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts index 011bd29a718..f51b642942e 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts @@ -9,10 +9,10 @@ import { MutableDataFrame, } from '@grafana/data'; -import { regionVariable } from '../__mocks__/CloudWatchDataSource'; -import { setupMockedLogsQueryRunner } from '../__mocks__/LogsQueryRunner'; -import { LogsRequestMock } from '../__mocks__/Request'; -import { validLogsQuery } from '../__mocks__/queries'; +import { regionVariable } from '../mocks/CloudWatchDataSource'; +import { setupMockedLogsQueryRunner } from '../mocks/LogsQueryRunner'; +import { LogsRequestMock } from '../mocks/Request'; +import { validLogsQuery } from '../mocks/queries'; import { CloudWatchLogsQuery } from '../types'; // Add this import statement import { LOGSTREAM_IDENTIFIER_INTERNAL, LOG_IDENTIFIER_INTERNAL } from './CloudWatchLogsQueryRunner'; diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts index 7654c6228cd..35978d1382b 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts @@ -11,10 +11,10 @@ import { dimensionVariable, periodIntervalVariable, accountIdVariable, -} from '../__mocks__/CloudWatchDataSource'; -import { initialVariableModelState } from '../__mocks__/CloudWatchVariables'; -import { setupMockedMetricsQueryRunner } from '../__mocks__/MetricsQueryRunner'; -import { validMetricSearchBuilderQuery, validMetricSearchCodeQuery } from '../__mocks__/queries'; +} from '../mocks/CloudWatchDataSource'; +import { initialVariableModelState } from '../mocks/CloudWatchVariables'; +import { setupMockedMetricsQueryRunner } from '../mocks/MetricsQueryRunner'; +import { validMetricSearchBuilderQuery, validMetricSearchCodeQuery } from '../mocks/queries'; import { MetricQueryType, MetricEditorMode, CloudWatchMetricsQuery } from '../types'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/plugins/datasource/cloudwatch/resources/ResourceAPI.test.ts b/public/app/plugins/datasource/cloudwatch/resources/ResourceAPI.test.ts index eeefcf20745..7406d48b330 100644 --- a/public/app/plugins/datasource/cloudwatch/resources/ResourceAPI.test.ts +++ b/public/app/plugins/datasource/cloudwatch/resources/ResourceAPI.test.ts @@ -1,4 +1,4 @@ -import { setupMockedResourcesAPI } from '../__mocks__/ResourcesAPI'; +import { setupMockedResourcesAPI } from '../mocks/ResourcesAPI'; describe('ResourcesAPI', () => { describe('describeLogGroup', () => { diff --git a/public/app/plugins/datasource/cloudwatch/tracking.test.ts b/public/app/plugins/datasource/cloudwatch/tracking.test.ts index ea54f98558b..822cf07a5d1 100644 --- a/public/app/plugins/datasource/cloudwatch/tracking.test.ts +++ b/public/app/plugins/datasource/cloudwatch/tracking.test.ts @@ -3,7 +3,7 @@ let handler: (e: DashboardLoadedEvent) => {}; import { config, reportInteraction } from '@grafana/runtime'; import './module'; -import { CloudWatchDashboardLoadedEvent } from './__mocks__/dashboardOnLoadedEvent'; +import { CloudWatchDashboardLoadedEvent } from './mocks/dashboardOnLoadedEvent'; import { CloudWatchQuery } from './types'; jest.mock('@grafana/runtime', () => { diff --git a/public/app/plugins/datasource/cloudwatch/utils/templateVariableUtils.test.ts b/public/app/plugins/datasource/cloudwatch/utils/templateVariableUtils.test.ts index 986cff080e3..676b3b7ea5d 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/templateVariableUtils.test.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/templateVariableUtils.test.ts @@ -1,6 +1,6 @@ import { CustomVariableModel } from '@grafana/data'; -import { logGroupNamesVariable, setupMockedTemplateService, regionVariable } from '../__mocks__/CloudWatchDataSource'; +import { logGroupNamesVariable, setupMockedTemplateService, regionVariable } from '../mocks/CloudWatchDataSource'; import { interpolateStringArrayUsingSingleOrMultiValuedVariable } from './templateVariableUtils'; diff --git a/public/app/plugins/datasource/cloudwatch/variables.test.ts b/public/app/plugins/datasource/cloudwatch/variables.test.ts index 74ec57a3f6b..6ea9116005c 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.test.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.test.ts @@ -1,10 +1,5 @@ -import { - dimensionVariable, - fieldsVariable, - labelsVariable, - setupMockedDataSource, -} from './__mocks__/CloudWatchDataSource'; -import { setupMockedResourcesAPI } from './__mocks__/ResourcesAPI'; +import { dimensionVariable, fieldsVariable, labelsVariable, setupMockedDataSource } from './mocks/CloudWatchDataSource'; +import { setupMockedResourcesAPI } from './mocks/ResourcesAPI'; import { VariableQuery, VariableQueryType } from './types'; import { CloudWatchVariableSupport } from './variables'; diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx index b6183ea3a16..e8f4695f2ba 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import { ConfigEditor } from './ConfigEditor'; -import { createDefaultConfigOptions } from './__mocks__/configOptions'; +import { createDefaultConfigOptions } from './mocks/configOptions'; describe('ConfigEditor', () => { it('should render without error', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx index 23d2d8def31..a0df1278d9f 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/ElasticDetails.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import selectEvent from 'react-select-event'; import { ElasticDetails } from './ElasticDetails'; -import { createDefaultConfigOptions } from './__mocks__/configOptions'; +import { createDefaultConfigOptions } from './mocks/configOptions'; describe('ElasticDetails', () => { describe('Max concurrent Shard Requests', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx index 40e707b7ad4..3f232df0215 100644 --- a/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx +++ b/public/app/plugins/datasource/elasticsearch/configuration/LogsConfig.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { LogsConfig } from './LogsConfig'; -import { createDefaultConfigOptions } from './__mocks__/configOptions'; +import { createDefaultConfigOptions } from './mocks/configOptions'; describe('ElasticDetails', () => { it('should pass correct data to onChange', () => { diff --git a/public/app/plugins/datasource/elasticsearch/configuration/__mocks__/configOptions.ts b/public/app/plugins/datasource/elasticsearch/configuration/mocks/configOptions.ts similarity index 100% rename from public/app/plugins/datasource/elasticsearch/configuration/__mocks__/configOptions.ts rename to public/app/plugins/datasource/elasticsearch/configuration/mocks/configOptions.ts diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.test.tsx b/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.test.tsx index 6c78486696b..ebaf5af0e68 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.test.tsx @@ -4,10 +4,10 @@ import userEvent from '@testing-library/user-event'; import { selectors } from '@grafana/e2e-selectors'; import { QueryEditor, Props } from './QueryEditor'; -import { scenarios } from './__mocks__/scenarios'; import { defaultQuery } from './constants'; import { TestDataQueryType } from './dataquery'; import { TestDataDataSource } from './datasource'; +import { scenarios } from './mocks/scenarios'; import { defaultStreamQuery } from './runStreams'; beforeEach(() => { diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/__mocks__/scenarios.ts b/public/app/plugins/datasource/grafana-testdata-datasource/mocks/scenarios.ts similarity index 100% rename from public/app/plugins/datasource/grafana-testdata-datasource/__mocks__/scenarios.ts rename to public/app/plugins/datasource/grafana-testdata-datasource/mocks/scenarios.ts diff --git a/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json b/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json index 418ba46835f..5d659d7553d 100644 --- a/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json +++ b/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json @@ -1,36 +1,142 @@ { "__inputs": [ { - "name": "DS_NAME", + "name": "datasource", + "description": "", "type": "datasource", - "pluginId": "graphite" + "pluginId": "graphite", + "pluginName": "Graphite" } ], - - "revision": "1.0", - "title": "Graphite Carbon Metrics", - "tags": ["graphite", "carbon"], - "timezone": "browser", + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "12.1.0-pre" + }, + { + "type": "datasource", + "id": "graphite", + "name": "Graphite", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "enable": false, + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, "editable": true, - "gnetId": null, + "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, + "links": [], "panels": [ { - "aliasColors": {}, - "annotate": { - "enable": false + "datasource": { + "type": "graphite", + "uid": "${datasource}" }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_NAME}", - "editable": true, - "fill": 0, - "fillGradient": 0, - "grid": { - "max": null, - "min": 0 + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Points Per Update" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "CPU" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] }, "gridPos": { "h": 10, @@ -38,111 +144,96 @@ "x": 0, "y": 0 }, - "hiddenSeries": false, "id": 1, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "loadingEditor": false, - "nullPointMode": "null as zero", "options": { - "dataLinks": [] - }, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "resolution": 100, - "scale": 1, - "seriesOverrides": [ - { - "alias": "Points Per Update", - "yaxis": 2 + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - { - "alias": "CPU", - "yaxis": 2 + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + }, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(sumSeries(carbon.agents.*.updateOperations),\"Updates\") " }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(sumSeries(carbon.agents.*.metricsReceived),'Metrics Received')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "alias(sumSeries(carbon.agents.*.committedPoints),'Committed Points')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "D", "target": "alias(sumSeries(carbon.agents.*.pointsPerUpdate),'Points Per Update')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "E", "target": "alias(averageSeries(carbon.agents.*.cpuUsage),'CPU')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "F", "target": "alias(sumSeries(carbon.agents.*.creates),'Creates')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "Graphite Carbon Metrics", - "tooltip": { - "query_as_alias": true, - "shared": false, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - }, - "zerofill": true + "type": "timeseries" } ], + "refresh": "", + "revision": "1.0", + "schemaVersion": 41, + "tags": ["graphite", "carbon"], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "includeAll": false, + "label": "", + "multi": false, + "name": "datasource", + "options": [], + "query": "graphite", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, "time": { "from": "now-3h", "to": "now" @@ -156,16 +247,9 @@ "status": "Stable", "type": "timepicker" }, - "templating": { - "enable": false, - "list": [] - }, - "annotations": { - "enable": false, - "list": [] - }, - "refresh": false, - "schemaVersion": 22, - "version": 3, - "links": [] + "timezone": "browser", + "title": "Graphite Carbon Metrics", + "uid": "15e2000b-24f6-4831-8993-aea2b84f852b", + "version": 4, + "weekStart": "" } diff --git a/public/app/plugins/datasource/graphite/dashboards/metrictank.json b/public/app/plugins/datasource/graphite/dashboards/metrictank.json index b70ad4d58ec..968139f7af0 100644 --- a/public/app/plugins/datasource/graphite/dashboards/metrictank.json +++ b/public/app/plugins/datasource/graphite/dashboards/metrictank.json @@ -1,17 +1,56 @@ { "__inputs": [ { - "name": "DS_NAME", + "name": "datasource", + "description": "", "type": "datasource", - "pluginId": "graphite" + "pluginId": "graphite", + "pluginName": "Graphite" + }, + { + "name": "DS_GDEV-TESTDATA", + "label": "gdev-testdata", + "description": "", + "type": "datasource", + "pluginId": "grafana-testdata-datasource", + "pluginName": "TestData" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "12.1.0-pre" + }, + { + "type": "datasource", + "id": "grafana-testdata-datasource", + "name": "TestData", + "version": "12.1.0-pre" + }, + { + "type": "datasource", + "id": "graphite", + "name": "Graphite", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" } ], - "annotations": { "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": { + "type": "datasource", + "uid": "grafana" + }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", @@ -24,10 +63,17 @@ ] }, "editable": true, + "fiscalYearStartMonth": 0, "graphTooltip": 1, + "id": null, + "links": [], "panels": [ { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -36,26 +82,341 @@ }, "id": 37, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "key stats", "type": "row" }, { - "aliasColors": { - "ES backpressure": "#052B51", - "add-to-closed": "#C15C17", - "primary": "#2F575E", - "store backpressure": "#962D82", - "too old": "#890F02" + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 90, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "stepAfter", + "lineWidth": 0, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "hertz" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "ES backpressure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#052B51", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "add-to-closed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#C15C17", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "primary" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#2F575E", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "store backpressure" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#962D82", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "too old" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890F02", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "too old" + }, + "properties": [ + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "custom.showPoints", + "value": "always" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "primary" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "min", + "value": 0 + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/metricdata.received/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#2a4422", + "mode": "fixed" + } + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/metricpoint.received/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#3f6833", + "mode": "fixed" + } + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/metricpoint_no_org.received/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#7eb26d", + "mode": "fixed" + } + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/invalid/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#C15C17", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/decode err/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890F02", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/unknown/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890F02", + "mode": "fixed" + } + } + ] + } + ] }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "editable": true, - "error": false, - "fill": 9, - "grid": {}, "gridPos": { "h": 6, "w": 6, @@ -63,162 +424,299 @@ "y": 1 }, "id": 9, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 0, - "links": [], - "nullPointMode": "null as zero", - "percentage": false, - "pointradius": 1, - "points": true, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "too old", - "lines": false, - "points": true + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false }, - { - "alias": "primary", - "fill": 3, - "lines": true, - "linewidth": 0, - "points": false, - "yaxis": 2 - }, - { - "alias": "/metricdata.received/", - "color": "#2a4422", - "lines": true, - "points": false, - "stack": "A" - }, - { - "alias": "/metricpoint.received/", - "color": "#3f6833", - "lines": true, - "points": false, - "stack": "A" - }, - { - "alias": "/metricpoint_no_org.received/", - "color": "#7eb26d", - "lines": true, - "points": false, - "stack": "A" - }, - { - "alias": "/invalid/", - "color": "#C15C17" - }, - { - "alias": "/decode err/", - "color": "#890F02" - }, - { - "alias": "/unknown/", - "color": "#890F02" + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": true, + }, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "aliasSub(sumSeries(perSecond(metrictank.stats.$environment.$instance.input.*.metrics_decode_err.counter32)), '.*\\.([^\\.]+)\\.metrics_decode_err.*', '\\1 decode err')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "groupByNodes(perSecond(metrictank.stats.$environment.$instance.input.*.*.received.counter32), 'sum', 5, 6, 7)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "groupByNodes(perSecond(metrictank.stats.$environment.$instance.input.*.*.discarded.invalid*.counter32), 'sum', 5, 6, 7, 8)" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "D", "target": "groupByNodes(perSecond(metrictank.stats.$environment.$instance.input.*.*.discarded.unknown.counter32), 'sum', 5, 6, 7, 8)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "E", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.tank.discarded.sample-out-of-order.counter32)), 'too old')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "F", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.tank.discarded.received-too-late.counter32)), 'add-to-saved')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "G", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.tank.metrics_reordered.counter32)), 'reordered')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "metrics in", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "hertz", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "percentunit", - "logBase": 1, - "max": null, - "min": 0, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": { - "95th": "#EAB839", - "999th": "#890F02", - "99th": "#99440A", - "GCd metrics/s": "#DEDAF7", - "mean": "#0A437C", - "median": "#3F6833", - "total_points": "#B7DBAB" + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#EAB839", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "999th" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890F02", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "99th" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#99440A", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "GCd metrics/s" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#DEDAF7", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "mean" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#0A437C", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "median" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#3F6833", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "total_points" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#B7DBAB", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "total_points" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "custom.lineWidth", + "value": 1 + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "GCd metrics/s" + }, + "properties": [ + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "custom.pointSize", + "value": 4 + }, + { + "id": "custom.showPoints", + "value": "always" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "heap objects" + }, + "properties": [ + { + "id": "custom.lineWidth", + "value": 1 + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "editable": true, - "error": false, - "fill": 0, - "grid": {}, "gridPos": { "h": 6, "w": 6, @@ -226,121 +724,267 @@ "y": 1 }, "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "links": [], - "nullPointMode": "connected", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "total_points", - "fill": 0, - "linewidth": 1, - "yaxis": 2 + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - { - "alias": "GCd metrics/s", - "lines": false, - "pointradius": 1, - "points": true - }, - { - "alias": "heap objects", - "linewidth": 1, - "yaxis": 2 + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + }, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "A", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.tank.total_points.gauge64), 'total_points')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.tank.metrics_active.gauge32), 'metrics_active')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.tank.gc_metric.counter32)), 'GCd metrics/s')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refId": "D", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.memory.gc.heap_objects.gauge64), 'heap objects')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "ringbuffer: Metrics & points", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": { - "primary": "#64B0C8", - "promotion wait": "#E5AC0E", - "ready": "#629E51", - "too old": "#890F02" + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "stepAfter", + "lineWidth": 2, + "pointSize": 4, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "primary" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#64B0C8", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "promotion wait" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#E5AC0E", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ready" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#629E51", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "too old" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890F02", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/primary/" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "max", + "value": 2 + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/ready/" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "max", + "value": 2 + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "editable": true, - "error": false, - "fill": 0, - "grid": {}, "gridPos": { "h": 6, "w": 6, @@ -348,111 +992,114 @@ "y": 1 }, "id": 17, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 2, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 1, - "points": true, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "/primary/", - "fill": 3, - "lines": true, - "linewidth": 0, - "points": false, - "stack": "A", - "yaxis": 2 + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false }, - { - "alias": "/ready/", - "fill": 3, - "lines": true, - "linewidth": 0, - "points": false, - "stack": "A", - "yaxis": 2 + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": true, + }, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "aliasByNode(metrictank.stats.$environment.$instance.cluster.self.state.primary.gauge1, 3, 7)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "aliasByNode(metrictank.stats.$environment.$instance.cluster.self.promotion_wait.gauge32, 3, 6)" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "aliasByNode(metrictank.stats.$environment.$instance.cluster.self.state.ready.gauge1, 3, 7)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "D", "target": "aliasByNode(metrictank.stats.$environment.$instance.cluster.self.priority.gauge32, 3, 6)" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "cluster status", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "logBase": 10, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "logBase": 1, - "max": 2, - "min": 0, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, "gridPos": { "h": 6, "w": 6, @@ -460,76 +1107,38 @@ "y": 1 }, "id": 29, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "aliasByNode(metrictank.stats.$environment.$instance.*.metrics_active.gauge32, 3,4)" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "metrics active", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { "collapsed": true, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -543,7 +1152,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fill": 1, "gridPos": { "h": 7, @@ -575,13 +1186,15 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "sortByName(aliasByNode(metrictank.stats.$environment.$instance.cluster.notifier.kafka.partition.*.lag.gauge64, 3, 8), true)" } ], "thresholds": [], - "timeFrom": null, - "timeShift": null, "title": "Partition lag - metricPersist", "tooltip": { "shared": true, @@ -590,33 +1203,24 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 10, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -624,7 +1228,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fill": 0, "gridPos": { "h": 7, @@ -657,13 +1263,15 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "sortByName(groupByNode(metrictank.stats.$environment.$instance.input.kafka-mdm.partition.*.lag.gauge64, 3, 'max'), true)" } ], "thresholds": [], - "timeFrom": null, - "timeShift": null, "title": "Partition lag - data", "tooltip": { "shared": true, @@ -672,42 +1280,45 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 10, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "kafka input plugin", "type": "row" }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -716,26 +1327,223 @@ }, "id": 39, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "memory, cpu and latency", "type": "row" }, { - "aliasColors": { - "GC cpu fraction - promille": "#BF1B00", - "alloc speed": "#C15C17", - "major_page_faults": "#bf1b00", - "minor_page_faults": "#0a437c", - "rss": "#ba43a9" + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "allocation", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "GC cpu fraction - promille" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#BF1B00", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "alloc speed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#C15C17", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "major_page_faults" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#bf1b00", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "minor_page_faults" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#0a437c", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "rss" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#ba43a9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "bytes_per_point" + }, + "properties": [ + { + "id": "custom.lineWidth", + "value": 1 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "major_page_faults" + }, + "properties": [ + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "minor_page_faults" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 50 + }, + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "rss" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 20 + }, + { + "id": "custom.lineWidth", + "value": 0 + } + ] + } + ] }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "editable": true, - "error": false, - "fill": 0, - "grid": {}, "gridPos": { "h": 9, "w": 8, @@ -743,148 +1551,273 @@ "y": 9 }, "id": 1, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 3, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "bytes_per_point", - "linewidth": 1, - "yaxis": 1 + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - { - "alias": "major_page_faults", - "yaxis": 2 - }, - { - "alias": "minor_page_faults", - "fill": 5, - "linewidth": 0, - "yaxis": 2 - }, - { - "alias": "rss", - "fill": 2, - "linewidth": 0 + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + }, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refCount": 0, "refId": "A", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.memory.total_bytes_allocated.counter64)), 'alloc rate')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refCount": 0, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.memory.bytes.allocated_in_heap.gauge64), 'allocated in heap')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": true, "refCount": 0, "refId": "C", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.memory.bytes.obtained_from_sys.gauge64), 'bytes_sys')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": true, "refCount": 0, "refId": "D", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.process.virtual_memory_bytes.gauge64), 'vsz')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refCount": 0, "refId": "E", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.process.resident_memory_bytes.gauge64), 'rss')" }, { + "datasource": { + "uid": "$datasource" + }, "refCount": 0, "refId": "G", "target": "aliasByNode(sumSeries(perSecond(metrictank.stats.$environment.$instance.process.minor_page_faults.counter64)), 5)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 0, "refId": "I", "target": "aliasByNode(sumSeries(perSecond(metrictank.stats.$environment.$instance.process.major_page_faults.counter64)), 5)" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "memory", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "bytes", - "label": "allocation", - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "none", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": { - "CPU usage": "#6d1f62", - "GC cpu fraction - promille": "#BF1B00", - "alloc speed": "#C15C17", - "major_page_faults": "#bf1b00", - "minor_page_faults": "#0a437c", - "rss": "#ba43a9" + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "CPU usage" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#6d1f62", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "GC cpu fraction - promille" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#BF1B00", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "alloc speed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#C15C17", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "major_page_faults" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#bf1b00", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "minor_page_faults" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#0a437c", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "rss" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#ba43a9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "GC cpu fraction - promille" + }, + "properties": [ + { + "id": "unit", + "value": "none" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "CPU usage" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 100 + }, + { + "id": "custom.lineWidth", + "value": 0 + } + ] + } + ] }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "editable": true, - "error": false, - "fill": 0, - "grid": {}, "gridPos": { "h": 9, "w": 8, @@ -892,106 +1825,231 @@ "y": 9 }, "id": 49, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 3, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "GC cpu fraction - promille", - "yaxis": 2 + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - { - "alias": "CPU usage", - "fill": 10, - "linewidth": 0 + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + }, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refCount": 0, "refId": "H", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.memory.gc.cpu_fraction.gauge32), 'GC cpu fraction - promille')" }, { + "datasource": { + "uid": "$datasource" + }, "refCount": 0, "refId": "F", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.process.cpu_seconds_total.counter64)), 'CPU usage')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "CPU", - "tooltip": { - "msResolution": true, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "percentunit", - "label": "", - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "none", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": { - "max": "#890F02", - "median": "#CCA300", - "p90": "#C15C17", - "reqs": "#2F575E" + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "max" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#890F02", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "median" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#CCA300", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#C15C17", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "reqs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#2F575E", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/idle/" + }, + "properties": [ + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.lineWidth", + "value": 0 + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "custom.stacking", + "value": { + "group": "A", + "mode": "normal" + } + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "reqs" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 0 + }, + { + "id": "custom.drawStyle", + "value": "line" + }, + { + "id": "custom.lineWidth", + "value": 1 + }, + { + "id": "custom.showPoints", + "value": "never" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "editable": true, - "error": false, - "fill": 1, - "grid": {}, "gridPos": { "h": 9, "w": 8, @@ -999,106 +2057,61 @@ "y": 9 }, "id": 5, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 2, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 1, - "points": true, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "/idle/", - "lines": true, - "linewidth": 0, - "points": false, - "stack": true, - "yaxis": 2 + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - { - "alias": "reqs", - "fill": 0, - "lines": true, - "linewidth": 1, - "nullPointMode": "null as zero", - "points": false, - "yaxis": 2 + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + }, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render*.latency.median.gauge32), 'median')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render*.latency.p90.gauge32), 'p90')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "alias(consolidateBy(maxSeries(metrictank.stats.$environment.$instance.api.request.render*.latency.max.gauge32), 'max'), 'max')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "E", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.api.request.render*.status.200.counter32)), 'reqs')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "render request duration (all steps)", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, "transparent": true, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { "collapsed": true, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -1112,7 +2125,9 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 1, @@ -1155,18 +2170,23 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(averageSeries(perSecond(metrictank.stats.$environment.$instance.memory.total_gc_cycles.counter64)), 'collections')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.memory.gc.last_duration.gauge64), 'duration')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "Golang GC", "tooltip": { "msResolution": false, @@ -1176,9 +2196,7 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -1186,21 +2204,16 @@ { "format": "hertz", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "ns", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -1210,6 +2223,10 @@ "bars": false, "dashLength": 10, "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "${datasource}" + }, "fill": 0, "gridPos": { "h": 5, @@ -1241,14 +2258,16 @@ "steppedLine": true, "targets": [ { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(metrictank.stats.$environment.$instance.memory.gc.gogc.sgauge32, 'gogc')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "GOGC", "tooltip": { "shared": true, @@ -1257,41 +2276,45 @@ }, "type": "graph", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "runtime", "type": "row" }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -1300,17 +2323,78 @@ }, "id": 40, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "requests stats 1", "type": "row" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, "gridPos": { "h": 7, "w": 8, @@ -1318,89 +2402,107 @@ "y": 20 }, "id": 35, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } }, - "lines": false, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 1, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.series.median.gauge32), 'median')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.series.max.gauge32), 'max')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.series.p90.gauge32), 'p90')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "Series Per Request", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, + "datasource": { + "uid": "$datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, "gridPos": { "h": 7, "w": 8, @@ -1408,101 +2510,171 @@ "y": 20 }, "id": 33, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } }, - "lines": false, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 1, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.points_fetched.median.gauge32), 'fetched-median')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.points_fetched.p90.gauge32), 'fetched-p90')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.api.request.render.points_fetched.max.gauge32), 'fetched-max')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "D", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.points_returned.median.gauge32), 'returned-median')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "E", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.points_returned.p90.gauge32), 'returned-p90')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "F", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.api.request.render.points_returned.max.gauge32), 'returned-max')" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "Points Per Request", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "fill": 1, + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsNull", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + } + ] + }, "gridPos": { "h": 7, "w": 8, @@ -1510,80 +2682,37 @@ "y": 20 }, "id": 34, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "hideEmpty": true, - "hideZero": true, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": false + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "aliasByNode(sortByName(sumSeriesWithWildcards(perSecond(metrictank.stats.$environment.$instance.api.request.*.status.*.counter32), 3), true), 5, 7)" } ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, "title": "Response Status per path", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } + "type": "timeseries" }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -1592,7 +2721,15 @@ }, "id": 41, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "requests stats 2", "type": "row" }, @@ -1607,10 +2744,14 @@ "mem-cass min": "#F9934E", "mem-cass p90": "#EF843C" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 0, @@ -1633,7 +2774,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -1645,50 +2785,76 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "A", "target": "alias(minSeries(metrictank.stats.$environment.$instance.api.requests_span.mem_and_cassandra.min.gauge32), 'mem-cass min')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": true, "refId": "B", "target": "alias(metrictank.stats.$environment.$instance.api.requests_span.mem_and_cassandra.p90.gauge32, 'mem-cass p90')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "C", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.requests_span.mem_and_cassandra.median.gauge32), 'mem-cass med')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "D", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.api.requests_span.mem_and_cassandra.max.gauge32), 'mem-cass max')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "E", "target": "alias(minSeries(metrictank.stats.$environment.$instance.api.requests_span.mem.min.gauge32), 'mem min')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "F", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.requests_span.mem.median.gauge32), 'mem med')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": true, "refId": "G", "target": "alias(metrictank.stats.$environment.$instance.api.requests_span.mem.p90.gauge32, 'mem p90')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "H", "target": "alias(minSeries(metrictank.stats.$environment.$instance.api.requests_span.mem.max.gauge32), 'mem max')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "requests span", "tooltip": { "msResolution": false, @@ -1696,11 +2862,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -1708,21 +2872,16 @@ { "format": "s", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "none", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -1736,10 +2895,13 @@ "mem-cass min": "#F9934E", "mem-cass p90": "#EF843C" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 0, @@ -1762,7 +2924,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -1774,20 +2935,25 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "A", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.api.requests_span.mem_and_cassandra.values.count32), 'mem-cass req/s')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.api.requests_span.mem.values.count32), 'mem req/s')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "series requests rate", "tooltip": { "msResolution": false, @@ -1795,11 +2961,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -1807,21 +2971,16 @@ { "format": "hertz", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "none", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -1832,10 +2991,14 @@ "store to iter": "#1F78C1", "total request handle": "#890F02" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 1, @@ -1858,7 +3021,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 1, @@ -1870,43 +3032,65 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.request.render.latency.mean.gauge32), 'total request handle')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.get_chunks.latency.mean.gauge32), 'store get chunks')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.to_iter.latency.mean.gauge32), 'store to iter')", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "D", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.get_target.latency.mean.gauge32), 'total getTarget')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refId": "E", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.api.iters_to_points.latency.mean.gauge32), 'iters to points')", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "F", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.mem.to_iter.latency.mean.gauge32), 'mem to iter')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refId": "G", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.plan.run.latency.mean.gauge32), 'plan run')", "textEditor": false } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "render request duration per step", "tooltip": { "msResolution": false, @@ -1915,11 +3099,9 @@ "value_type": "cumulative" }, "transparent": true, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -1927,25 +3109,24 @@ { "format": "ms", "logBase": 2, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -1954,7 +3135,15 @@ }, "id": 42, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "store stats 1", "type": "row" }, @@ -1963,10 +3152,14 @@ "num write-workers": "#052B51", "write queue items limit (each)": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 0, @@ -1989,7 +3182,6 @@ }, "lines": true, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 5, @@ -2017,30 +3209,42 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "A", "target": "aliasSub(sumSeriesWithWildcards(metrictank.stats.$environment.$instance.store.*.write_queue.*.items.max.gauge32, 3), '.*write_queue.(.*).items.*', 'write_queue-\\1')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.store.*.write_queue.size.gauge32), 'write queue items limit (each)')" }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "C", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.store.*.num_writers.gauge32), 'num write-workers')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "D", "target": "alias(maxSeries(consolidateBy(metrictank.stats.$environment.$instance.persist.latency.max.gauge32, 'max')), 'persist-duration max')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "write workers & queues", "tooltip": { "msResolution": false, @@ -2048,11 +3252,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2060,21 +3262,16 @@ { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2082,10 +3279,13 @@ "message_size.max": "#EF843C", "message_size.mean": "#2F575E" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 0, @@ -2108,7 +3308,6 @@ }, "lines": true, "linewidth": 2, - "links": [], "nullPointMode": "null as zero", "percentage": false, "pointradius": 1, @@ -2135,22 +3334,36 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "groupByNodes(metrictank.stats.$environment.$instance.cluster.notifier.*.message_size.mean.gauge32, 'avg', 6, 7, 8)", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "aliasByNode(sumSeries(perSecond(metrictank.stats.$environment.$instance.cluster.notifier.*.messages-published.counter32)), 5, 6)", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": false, "refId": "C", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.cluster.notifier.all.messages-received.counter32)), 'messages_received')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refId": "D", "target": "groupByNodes(metrictank.stats.$environment.$instance.cluster.notifier.*.message_size.max.gauge32, 'max',6, 7, 8)", @@ -2158,9 +3371,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "metricpersist", "tooltip": { "msResolution": true, @@ -2168,35 +3379,26 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "hertz", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2212,10 +3414,14 @@ "put p90": "#EA6460", "put/s": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 0, @@ -2238,7 +3444,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -2255,29 +3460,41 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "E", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.get.wait.latency.p90.gauge32), 'get p90')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "F", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.put.wait.latency.p90.gauge32), 'put p90')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.store.*.omitted_old_reads.counter32)), 'dropped reads')", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.store.*.read_queue_full.counter32)), 'read queue full')", "textEditor": false } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "store queue wait durations", "tooltip": { "msResolution": false, @@ -2285,11 +3502,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2297,25 +3512,24 @@ { "format": "ms", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -2324,7 +3538,15 @@ }, "id": 43, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "store stats 2", "type": "row" }, @@ -2337,10 +3559,13 @@ "size at save median": "#052B51", "size at save p90": "#65C5DB" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 1, @@ -2363,7 +3588,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -2375,34 +3599,53 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.chunk_size.at_load.median.gauge32), 'size at load median')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.chunk_size.at_load.p90.gauge32), 'size at load p90')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.chunk_size.at_load.max.gauge32), 'size at load max')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "D", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.chunk_size.at_save.median.gauge32), 'size at save median')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "E", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.chunk_size.at_save.p90.gauge32), 'size at save p90')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "F", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.chunk_size.at_save.max.gauge32), 'size at save max')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "chunk sizes", "tooltip": { "msResolution": false, @@ -2410,11 +3653,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2422,21 +3663,16 @@ { "format": "bytes", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2449,10 +3685,14 @@ "put p90": "#F29191", "put/s": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 0, @@ -2475,7 +3715,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -2487,18 +3726,23 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.store.*.put.exec.values.rate32), 'put/s')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.store.*.get.exec.values.rate32), 'get/s')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "store req/s", "tooltip": { "msResolution": false, @@ -2506,11 +3750,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2518,21 +3760,16 @@ { "format": "hertz", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2547,10 +3784,13 @@ "put p90": "#F29191", "put/s": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 0, @@ -2573,7 +3813,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -2585,26 +3824,38 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.put.exec.latency.p90.gauge32), 'put p90')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.put.exec.latency.mean.gauge32), 'put mean')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.get.exec.latency.p90.gauge32), 'get p90')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "D", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.get.exec.latency.mean.gauge32), 'get mean')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "store query durations", "tooltip": { "msResolution": false, @@ -2612,11 +3863,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2624,25 +3873,24 @@ { "format": "ms", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -2651,7 +3899,15 @@ }, "id": 44, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "store stats 3", "type": "row" }, @@ -2664,10 +3920,14 @@ "rows per resp med": "#052B51", "rows per resp min": "#6ED0E0" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 0, @@ -2691,7 +3951,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -2713,34 +3972,53 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.chunks_per_row.median.gauge32), 'chunks per row med')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(minSeries(metrictank.stats.$environment.$instance.store.*.chunks_per_row.min.gauge32), 'chunks per row min')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.chunks_per_row.max.gauge32), 'chunks per row max')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "D", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.store.*.rows_per_response.median.gauge32), 'rows per resp med')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "E", "target": "alias(minSeries(metrictank.stats.$environment.$instance.store.*.rows_per_response.min.gauge32), 'rows per resp min')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "F", "target": "alias(maxSeries(metrictank.stats.$environment.$instance.store.*.rows_per_response.max.gauge32), 'rows per resp max')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "store responses", "tooltip": { "msResolution": false, @@ -2748,11 +4026,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2760,21 +4036,16 @@ { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2786,10 +4057,13 @@ "too-many-timeouts": "#BA43A9", "unavailable": "#890F02" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 1, @@ -2812,7 +4086,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 1, @@ -2824,18 +4097,23 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "aliasByNode(sumSeriesWithWildcards(perSecond(metrictank.stats.$environment.$instance.store.*.error.*.counter32), 2, 3), 5)" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "aliasByNode(metrictank.stats.$environment.$instance.store.*.omit_read.*.counter32, 7)" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "store errors", "tooltip": { "msResolution": false, @@ -2843,35 +4121,26 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "hertz", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -2880,10 +4149,14 @@ "save_fail/s": "#BF1B00", "save_ok/s": "#3F6833" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 0, @@ -2906,7 +4179,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -2918,14 +4190,15 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "aliasByNode(sumSeriesWithWildcards(perSecond(metrictank.stats.$environment.$instance.tank.chunk_operations.*.counter32), 2, 3), 4)" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "chunk operations", "tooltip": { "msResolution": false, @@ -2933,11 +4206,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -2945,25 +4216,24 @@ { "format": "hertz", "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -2972,16 +4242,28 @@ }, "id": 45, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "index", "type": "row" }, { "aliasColors": {}, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "fill": 1, "gridPos": { "h": 7, @@ -3001,7 +4283,6 @@ }, "lines": true, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, @@ -3013,50 +4294,42 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.idx.metrics_active.gauge32), 'num')", "textEditor": false } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "num metrics", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3067,10 +4340,14 @@ "metricdef lookup duration median": "#E0752D", "metricdef lookup duration p90": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 1, @@ -3095,7 +4372,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 1, @@ -3115,28 +4391,40 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "groupByNodes(metrictank.stats.$environment.$instance.idx.*.*.latency.mean.gauge32, 'avg',5, 6, 8)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "groupByNodes(consolidateBy(metrictank.stats.$environment.$instance.idx.*.*.latency.{p90,max}.gauge32, 'max'), 'max', 5, 6, 8)" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "groupByNodes(metrictank.stats.$environment.$instance.idx.*.*.*.latency.mean.gauge32, 'avg', 5, 6, 7, 9)", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "D", "target": "groupByNodes(consolidateBy(metrictank.stats.$environment.$instance.idx.*.*.*.latency.{p90,max}.gauge32, 'max'), 'max', 5, 6, 7, 9)", "textEditor": false } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "op durations", "tooltip": { "msResolution": false, @@ -3144,11 +4432,9 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, @@ -3156,21 +4442,16 @@ { "format": "ms", "logBase": 2, - "max": null, - "min": null, "show": true }, { "format": "ms", "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3192,10 +4473,13 @@ "p75": "#0A50A1", "p90": "#806EB7" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "decimals": 0, "editable": true, "error": false, @@ -3221,7 +4505,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 2, @@ -3241,25 +4524,34 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "groupByNodes(perSecond(metrictank.stats.$environment.$instance.idx.*.ops.*.counter32), 'sum', 5, 7)", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "aliasByNode(sumSeriesWithWildcards(metrictank.stats.$environment.$instance.idx.*.add.values.rate32, 2, 3), 3, 4)", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "aliasByNode(sumSeriesWithWildcards(metrictank.stats.$environment.$instance.idx.*.query-insert.exec.values.rate32, 2, 3), 3, 4)", "textEditor": false } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "operation rates", "tooltip": { "msResolution": true, @@ -3267,35 +4559,27 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "ms", - "label": null, "logBase": 1, - "max": null, "min": 0, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3307,10 +4591,13 @@ "too-many-timeouts": "#BA43A9", "unavailable": "#890F02" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 1, @@ -3333,7 +4620,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 1, @@ -3345,22 +4631,31 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "groupByNode(perSecond(metrictank.stats.$environment.$instance.idx.*.error.*.counter32), 7, 'sum')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "B", "target": "groupByNode(perSecond(metrictank.stats.$environment.$instance.idx.*.save.skipped.counter32), 7, 'sum')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "C", "target": "groupByNode(perSecond(metrictank.stats.$environment.$instance.recovered_errors.idx.*.*.counter32), 7, 'sum')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "idx errors", "tooltip": { "msResolution": false, @@ -3368,45 +4663,39 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "hertz", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "aliasColors": { "entries": "#1f78c1" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fill": 0, "gridPos": { "h": 4, @@ -3426,7 +4715,6 @@ }, "lines": true, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, @@ -3452,55 +4740,52 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 0, "refId": "B", "target": "aliasByNode(metrictank.stats.$environment.$instance.idx.memory.find-cache.entries.gauge32, 7, 3)" }, { + "datasource": { + "uid": "$datasource" + }, "refCount": 0, "refId": "C", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.idx.memory.find-cache.backoff.gauge32), 'backoff')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "find cache entries & backoff", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, "min": "0", "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3508,10 +4793,14 @@ "hit-rate": "#1f78c1", "miss": "#e0752d" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "fill": 10, "gridPos": { "h": 4, @@ -3531,7 +4820,6 @@ }, "lines": true, "linewidth": 0, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, @@ -3552,11 +4840,18 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refCount": 1, "refId": "A", "target": "groupByNode(metrictank.stats.$environment.$instance.idx.memory.find-cache.ops.*.rate32, 8, 'sum')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "hide": true, "refCount": 1, "refId": "B", @@ -3564,6 +4859,9 @@ "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refCount": 0, "refId": "C", "target": "alias(asPercent(sumSeries(keepLastValue(metrictank.stats.$environment.$instance.idx.memory.find-cache.ops.hit.rate32, 10)), #B), 'hit-rate')", @@ -3571,44 +4869,33 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "find cache hit/miss", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "hertz", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "percent", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3617,10 +4904,14 @@ "exec": "#5195ce", "recv": "#962d82" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "fill": 10, "gridPos": { "h": 4, @@ -3640,7 +4931,6 @@ }, "lines": true, "linewidth": 0, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, @@ -3652,6 +4942,9 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refCount": 0, "refId": "A", "target": "groupByNode(metrictank.stats.$environment.$instance.idx.memory.find-cache.invalidation.*.rate32, 8, 'sum')", @@ -3659,48 +4952,41 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "find cache invalidations", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -3709,16 +4995,28 @@ }, "id": 46, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "stats reporting", "type": "row" }, { "aliasColors": {}, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 1, @@ -3741,7 +5039,6 @@ }, "lines": true, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 5, @@ -3753,14 +5050,15 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "aliasByNode(metrictank.stats.$environment.$instance.stats.message_size.gauge32, 3)" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "message size", "tooltip": { "msResolution": false, @@ -3768,35 +5066,26 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3804,10 +5093,14 @@ "queue-items": "#7EB26D", "queue-limit": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 1, @@ -3830,7 +5123,6 @@ }, "lines": true, "linewidth": 2, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, @@ -3849,22 +5141,30 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.stats.graphite.connected.gauge1), 'connected')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.stats.graphite.flush.latency.p90.gauge32), 'flush latency')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.stats.graphite.flush.values.rate32), 'flush rate')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "conn to graphite", "tooltip": { "msResolution": false, @@ -3872,35 +5172,26 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -3908,10 +5199,14 @@ "queue-items": "#7EB26D", "queue-limit": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 1, @@ -3934,7 +5229,6 @@ }, "lines": false, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 1, @@ -3946,18 +5240,23 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.stats.graphite.write_queue.size.gauge32), 'queue-limit')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(metrictank.stats.$environment.$instance.stats.graphite.write_queue.items.max.gauge32, 'queue-items')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "queue to graphite", "tooltip": { "msResolution": false, @@ -3965,39 +5264,34 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -4006,7 +5300,15 @@ }, "id": 47, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "chunk cache", "type": "row" }, @@ -4015,10 +5317,13 @@ "max": "#890F02", "used": "#3F6833" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 0, @@ -4041,7 +5346,6 @@ }, "lines": true, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 5, @@ -4065,17 +5369,28 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 1, "refId": "A", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.cache.size.max.gauge64), 'max')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refCount": 0, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.cache.size.used.gauge64), 'used')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 0, "refId": "C", "target": "alias(divideSeries(#B,#A), 'utilisation')", @@ -4083,6 +5398,9 @@ "textEditor": true }, { + "datasource": { + "uid": "$datasource" + }, "hide": false, "refCount": 0, "refId": "D", @@ -4090,6 +5408,10 @@ "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 0, "refId": "E", "target": "alias(sum(#B, #D),'total used')", @@ -4098,9 +5420,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "size", "tooltip": { "msResolution": false, @@ -4108,35 +5428,26 @@ "sort": 1, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "bytes", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "percentunit", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -4146,10 +5457,13 @@ "used": "#3F6833", "utilisation": "#6ed0e0" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "editable": true, "error": false, "fill": 0, @@ -4172,7 +5486,6 @@ }, "lines": true, "linewidth": 2, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 5, @@ -4204,24 +5517,38 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 1, "refId": "A", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.cache.accounting.queue.size.max.gauge64), 'max')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "refCount": 1, "refId": "B", "target": "alias(sumSeries(metrictank.stats.$environment.$instance.cache.accounting.queue.size.used.max.gauge32), 'used')", "textEditor": false }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refCount": 1, "refId": "C", "target": "alias(averageSeries(metrictank.stats.$environment.$instance.cache.accounting.queue.add.latency.p90.gauge32), 'add-latency-p90')", "textEditor": false }, { + "datasource": { + "uid": "$datasource" + }, "hide": true, "refCount": 0, "refId": "D", @@ -4231,9 +5558,7 @@ } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "accounting", "tooltip": { "msResolution": false, @@ -4241,35 +5566,26 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "none", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -4282,10 +5598,14 @@ "metric evict": "#6D1F62", "miss": "#BF1B00" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "editable": true, "error": false, "fill": 10, @@ -4308,7 +5628,6 @@ }, "lines": true, "linewidth": 0, - "links": [], "nullPointMode": "connected", "percentage": false, "pointradius": 5, @@ -4341,26 +5660,38 @@ "steppedLine": false, "targets": [ { + "datasource": { + "uid": "$datasource" + }, "refId": "A", "target": "aliasByNode(sumSeriesWithWildcards(perSecond(metrictank.stats.$environment.$instance.cache.ops.metric.{hit-*,miss}.counter32), 2, 3), 5)" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.cache.ops.metric.evict.counter32)), 'metric evict')" }, { + "datasource": { + "uid": "$datasource" + }, "refId": "C", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.cache.ops.chunk.evict.counter32)), 'chunk evict')" }, { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "D", "target": "alias(sumSeries(perSecond(metrictank.stats.$environment.$instance.recovered_errors.cache.metric.searchForwardBug.counter32)), 'cache-bug-surpress')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "operations", "tooltip": { "msResolution": false, @@ -4368,39 +5699,34 @@ "sort": 0, "value_type": "cumulative" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "collapsed": false, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, "gridPos": { "h": 1, "w": 24, @@ -4409,7 +5735,15 @@ }, "id": 48, "panels": [], - "repeat": null, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], "title": "clustering", "type": "row" }, @@ -4423,10 +5757,13 @@ "total.secondary-ready": "#2F575E", "update": "light-purple" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fill": 1, "gridPos": { "h": 6, @@ -4446,7 +5783,6 @@ }, "lines": false, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -4458,57 +5794,53 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "groupByNode(perSecond(metrictank.stats.$environment.$instance.cluster.events.*.counter32), 6, 'sum')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "gossip events", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { "aliasColors": {}, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fill": 1, "gridPos": { "h": 11, @@ -4528,7 +5860,6 @@ }, "lines": true, "linewidth": 1, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 5, @@ -4540,49 +5871,42 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "A", "target": "aliasByNode(sumSeries(perSecond(metrictank.stats.$environment.$instance.cluster.decode_err.*.counter32)), 6)" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "gossip decode errors", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } }, { @@ -4601,10 +5925,13 @@ "total.secondary-ready": "#2F575E", "update": "#511749" }, + "autoMigrateFrom": "graph", "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "fill": 4, "gridPos": { "h": 5, @@ -4624,7 +5951,6 @@ }, "lines": true, "linewidth": 0, - "links": [], "nullPointMode": "null", "percentage": false, "pointradius": 1, @@ -4636,63 +5962,52 @@ "steppedLine": false, "targets": [ { + "datasource": { + "type": "graphite", + "uid": "${datasource}" + }, "refId": "B", "target": "groupByNode(metrictank.stats.$environment.$instance.cluster.total.state.*.gauge32, 7, 'sum')" } ], "thresholds": [], - "timeFrom": null, "timeRegions": [], - "timeShift": null, "title": "gossip events and state", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "type": "graph", + "type": "timeseries", "xaxis": { - "buckets": null, "mode": "time", - "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true }, { "format": "short", - "label": null, "logBase": 1, - "max": null, - "min": null, "show": true } ], "yaxis": { - "align": false, - "alignLevel": null + "align": false } } ], - "refresh": false, - "schemaVersion": 19, + "refresh": "", + "schemaVersion": 41, "tags": [], "templating": { "list": [ { - "current": { - "text": "default", - "value": "default" - }, - "datasource": null, + "current": {}, "hide": 0, "includeAll": false, "label": "", @@ -4707,13 +6022,14 @@ }, { "allFormat": "glob", - "allValue": null, "current": {}, - "datasource": "$datasource", + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "$datasource" + }, "definition": "", "hide": 0, "includeAll": false, - "label": null, "multi": false, "multiFormat": "glob", "name": "environment", @@ -4725,23 +6041,20 @@ "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", - "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allFormat": "glob", - "allValue": null, - "current": { - "text": "All", - "value": "$__all" + "current": {}, + "datasource": { + "type": "graphite", + "uid": "$datasource" }, - "datasource": "$datasource", "definition": "", "hide": 0, "includeAll": true, - "label": null, "multi": true, "multiFormat": "glob", "name": "instance", @@ -4753,7 +6066,6 @@ "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", - "tags": [], "tagsQuery": "", "type": "query", "useTags": false @@ -4775,5 +6087,7 @@ }, "timezone": "utc", "title": "Metrictank", - "uid": "tQW3QShiz" + "uid": "tQW3QShiz", + "version": 3, + "weekStart": "" } diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts index a9d8d48a044..cb01348fdcd 100644 --- a/public/app/plugins/datasource/graphite/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -1,4 +1,5 @@ import { isArray } from 'lodash'; +import moment from 'moment'; import { of } from 'rxjs'; import { createFetchResponse } from 'test/helpers/createFetchResponse'; @@ -6,6 +7,7 @@ import { AbstractLabelMatcher, AbstractLabelOperator, DataQueryRequest, + dateMath, dateTime, getFrameDisplayName, MetricFindValue, @@ -956,6 +958,31 @@ describe('graphiteDatasource', () => { await assertQueryExport('interpolate(alias(servers.west.001))', []); }); }); + + describe('translateTime', () => { + it('does not mutate passed in date', async () => { + const date = new Date('2025-06-30T00:00:59.000Z'); + const functionDate = moment(date); + const updatedDate = ctx.ds.translateTime( + dateMath.toDateTime(functionDate.toDate(), { roundUp: undefined, timezone: undefined })!, + true + ); + + expect(functionDate.toDate()).toEqual(date); + expect(updatedDate).not.toEqual(date.getTime()); + }); + it('does not mutate passed in relative date - string', async () => { + const date = 'now-1m'; + const updatedDate = ctx.ds.translateTime(date, true); + + expect(updatedDate).not.toEqual(date); + }); + it('returns the input if the input is invalid', async () => { + const updatedDate = ctx.ds.translateTime('', true); + + expect(updatedDate).toBe(''); + }); + }); }); function accessScenario(name: string, url: string, fn: ({ headers }: { headers: Record }) => void) { diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 9516487169b..d5415d0eed5 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -1,4 +1,5 @@ import { map as _map, each, indexOf, isArray, isString } from 'lodash'; +import moment from 'moment'; import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; @@ -12,16 +13,17 @@ import { DataSourceApi, DataSourceWithQueryExportSupport, dateMath, + DateTime, dateTime, getSearchFilterScopedVar, MetricFindValue, QueryResultMetaStat, ScopedVars, TimeRange, - TimeZone, toDataFrame, } from '@grafana/data'; import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime'; +import { TimeZone } from '@grafana/schema'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; import { getRollupNotice, getRuntimeConsolidationNotice } from 'app/plugins/datasource/graphite/meta'; @@ -500,17 +502,32 @@ export class GraphiteDatasource return this.templateSrv.containsTemplate(target.target ?? ''); } - translateTime(date: any, roundUp?: boolean, timezone?: TimeZone) { - if (isString(date)) { - if (date === 'now') { - return 'now'; - } else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) { - date = date.substring(3); - date = date.replace('m', 'min'); - date = date.replace('M', 'mon'); - return date; + translateTime(date: DateTime | string, roundUp?: boolean, timezone?: TimeZone) { + const parseDate = () => { + if (isString(date)) { + if (date === 'now') { + return 'now'; + } else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) { + return date.substring(3).replace('m', 'min').replace('M', 'mon'); + } + const parsedDate = dateMath.toDateTime(date, { roundUp, timezone }); + + // If the date is invalid return the original string + // e.g. if an empty string is passed in or if the roundng is invalid e.g. now/2y + if (!parsedDate || parsedDate.isValid() === false) { + return date; + } + + return moment(parsedDate.toDate()); + } else { + return moment(date.toDate()); } - date = dateMath.parse(date, roundUp, timezone); + }; + + const parsedDate = parseDate(); + + if (typeof parsedDate === 'string') { + return parsedDate; } // graphite' s from filter is exclusive @@ -518,16 +535,16 @@ export class GraphiteDatasource // to guarantee that we get all the data that // exists for the specified range if (roundUp) { - if (date.get('s')) { - date.add(1, 's'); + if (parsedDate.get('s')) { + parsedDate.add(1, 's'); } } else if (roundUp === false) { - if (date.get('s')) { - date.subtract(1, 's'); + if (parsedDate.get('s')) { + parsedDate.subtract(1, 's'); } } - return date.unix(); + return parsedDate.unix(); } metricFindQuery(findQuery: string | GraphiteQuery, optionalOptions?: any): Promise { diff --git a/public/app/plugins/datasource/influxdb/__mocks__/query.ts b/public/app/plugins/datasource/influxdb/__mocks__/query.ts deleted file mode 100644 index 0d74ecf113e..00000000000 --- a/public/app/plugins/datasource/influxdb/__mocks__/query.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { DataQueryRequest, dateTime } from '@grafana/data'; - -import { InfluxQuery } from '../types'; - -const now = dateTime('2023-09-16T21:26:00Z'); - -export const queryOptions: DataQueryRequest = { - app: 'dashboard', - interval: '10', - intervalMs: 10, - requestId: 'A-testing', - startTime: 0, - range: { - from: dateTime(now).subtract(15, 'minutes'), - to: now, - raw: { - from: 'now-15m', - to: 'now', - }, - }, - rangeRaw: { - from: 'now-15m', - to: 'now', - }, - targets: [], - timezone: 'UTC', - scopedVars: { - interval: { text: '1m', value: '1m' }, - __interval: { text: '1m', value: '1m' }, - __interval_ms: { text: 60000, value: 60000 }, - }, -}; diff --git a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.test.tsx b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.test.tsx index 7f6f4c13306..837a56b85e4 100644 --- a/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/VisualInfluxQLEditor.test.tsx @@ -1,7 +1,7 @@ import { render, waitFor } from '@testing-library/react'; -import { getMockInfluxDS, getMockDSInstanceSettings } from '../../../../../__mocks__/datasource'; import InfluxDatasource from '../../../../../datasource'; +import { getMockInfluxDS, getMockDSInstanceSettings } from '../../../../../mocks/datasource'; import { DEFAULT_POLICY, InfluxQuery } from '../../../../../types'; import { VisualInfluxQLEditor } from './VisualInfluxQLEditor'; diff --git a/public/app/plugins/datasource/influxdb/datasource.test.ts b/public/app/plugins/datasource/influxdb/datasource.test.ts index cc393af0760..4633f20744a 100644 --- a/public/app/plugins/datasource/influxdb/datasource.test.ts +++ b/public/app/plugins/datasource/influxdb/datasource.test.ts @@ -6,11 +6,11 @@ import config from 'app/core/config'; import { queryBuilder } from '../../../features/variables/shared/testing/builders'; -import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService, replaceMock } from './__mocks__/datasource'; -import { mockInfluxQueryRequest } from './__mocks__/request'; -import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './__mocks__/response'; import { BROWSER_MODE_DISABLED_MESSAGE } from './constants'; import InfluxDatasource from './datasource'; +import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService, replaceMock } from './mocks/datasource'; +import { mockInfluxQueryRequest } from './mocks/request'; +import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './mocks/response'; import { InfluxQuery, InfluxVersion } from './types'; const fetchMock = mockBackendService(mockInfluxFetchResponse()); diff --git a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts index 72d940d5ad0..21aa827537d 100644 --- a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts +++ b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts @@ -3,10 +3,10 @@ import { lastValueFrom } from 'rxjs'; import { SQLQuery } from '@grafana/sql'; import config from 'app/core/config'; -import { getMockDSInstanceSettings, mockBackendService, mockTemplateSrv } from './__mocks__/datasource'; -import { mockInfluxQueryRequest } from './__mocks__/request'; -import { mockInfluxSQLFetchResponse } from './__mocks__/response'; import InfluxDatasource from './datasource'; +import { getMockDSInstanceSettings, mockBackendService, mockTemplateSrv } from './mocks/datasource'; +import { mockInfluxQueryRequest } from './mocks/request'; +import { mockInfluxSQLFetchResponse } from './mocks/response'; import { InfluxVersion } from './types'; config.featureToggles.influxdbBackendMigration = true; diff --git a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts index bea4f7bfc98..45a0d3df96e 100644 --- a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts +++ b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts @@ -1,7 +1,7 @@ import { TemplateSrv } from '@grafana/runtime'; -import { getMockDSInstanceSettings, mockBackendService } from '../__mocks__/datasource'; -import { mockInfluxSQLVariableFetchResponse } from '../__mocks__/response'; +import { getMockDSInstanceSettings, mockBackendService } from '../mocks/datasource'; +import { mockInfluxSQLVariableFetchResponse } from '../mocks/response'; import { FlightSQLDatasource } from './datasource.flightsql'; diff --git a/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts b/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts index e39d42fd83e..c657abed2fd 100644 --- a/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts +++ b/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts @@ -1,7 +1,7 @@ import config from 'app/core/config'; -import { getMockInfluxDS } from './__mocks__/datasource'; import { getAllMeasurements, getAllPolicies, getFieldKeys, getTagKeys, getTagValues } from './influxql_metadata_query'; +import { getMockInfluxDS } from './mocks/datasource'; import { InfluxQuery, InfluxVariableQuery } from './types'; describe('influx_metadata_query', () => { diff --git a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts index 30b9c86b984..bf83c0683b3 100644 --- a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts +++ b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts @@ -1,5 +1,5 @@ -import { templateSrvStub as templateService } from './__mocks__/datasource'; import { buildMetadataQuery } from './influxql_query_builder'; +import { templateSrvStub as templateService } from './mocks/datasource'; import { DEFAULT_POLICY } from './types'; describe('influxql-query-builder', () => { diff --git a/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts b/public/app/plugins/datasource/influxdb/mocks/datasource.ts similarity index 100% rename from public/app/plugins/datasource/influxdb/__mocks__/datasource.ts rename to public/app/plugins/datasource/influxdb/mocks/datasource.ts diff --git a/public/app/plugins/datasource/influxdb/__mocks__/request.ts b/public/app/plugins/datasource/influxdb/mocks/request.ts similarity index 100% rename from public/app/plugins/datasource/influxdb/__mocks__/request.ts rename to public/app/plugins/datasource/influxdb/mocks/request.ts diff --git a/public/app/plugins/datasource/influxdb/__mocks__/response.ts b/public/app/plugins/datasource/influxdb/mocks/response.ts similarity index 100% rename from public/app/plugins/datasource/influxdb/__mocks__/response.ts rename to public/app/plugins/datasource/influxdb/mocks/response.ts diff --git a/public/app/plugins/datasource/influxdb/response_parser.test.ts b/public/app/plugins/datasource/influxdb/response_parser.test.ts index e206dca77a8..247e1b74491 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.test.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.test.ts @@ -6,8 +6,8 @@ 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 { getMockDSInstanceSettings, getMockInfluxDS } from './__mocks__/datasource'; import InfluxQueryModel from './influx_query_model'; +import { getMockDSInstanceSettings, getMockInfluxDS } from './mocks/datasource'; import ResponseParser, { getSelectedParams } from './response_parser'; import { InfluxQuery } from './types'; diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index 6a888eedec2..eb449333632 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -2,11 +2,11 @@ import { AbstractLabelOperator, DataFrame, TimeRange, dateTime, getDefaultTimeRa import { config } from '@grafana/runtime'; import LanguageProvider from './LanguageProvider'; -import { createDetectedFieldValuesMetadataRequest } from './__mocks__/createDetectedFieldValuesMetadataRequest'; -import { createDetectedFieldsMetadataRequest } from './__mocks__/createDetectedFieldsMetadataRequest'; -import { createLokiDatasource } from './__mocks__/datasource'; -import { createMetadataRequest } from './__mocks__/metadataRequest'; import { DEFAULT_MAX_LINES_SAMPLE, LokiDatasource } from './datasource'; +import { createDetectedFieldValuesMetadataRequest } from './mocks/createDetectedFieldValuesMetadataRequest'; +import { createDetectedFieldsMetadataRequest } from './mocks/createDetectedFieldsMetadataRequest'; +import { createLokiDatasource } from './mocks/datasource'; +import { createMetadataRequest } from './mocks/metadataRequest'; import { extractLogParserFromDataFrame, extractLabelKeysFromDataFrame, @@ -681,14 +681,6 @@ describe('Query imports', () => { }); describe('getParserAndLabelKeys()', () => { - const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints; - beforeAll(() => { - config.featureToggles.lokiQueryHints = true; - }); - afterAll(() => { - config.featureToggles.lokiQueryHints = queryHintsFeatureToggle; - }); - let datasource: LokiDatasource, languageProvider: LanguageProvider; const extractLogParserFromDataFrameMock = jest.mocked(extractLogParserFromDataFrame); const extractedLabelKeys = ['extracted', 'label']; @@ -810,12 +802,6 @@ describe('Query imports', () => { mockTimeRange ); }); - it('does not call dataSample with feature toggle disabled', async () => { - config.featureToggles.lokiQueryHints = false; - jest.spyOn(datasource, 'getDataSamples'); - languageProvider.getParserAndLabelKeys('{place="luna"}', { timeRange: mockTimeRange }); - expect(datasource.getDataSamples).not.toHaveBeenCalled(); - }); }); }); diff --git a/public/app/plugins/datasource/loki/LanguageProvider.ts b/public/app/plugins/datasource/loki/LanguageProvider.ts index d6bae1ea6d1..5607980b557 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.ts @@ -477,9 +477,6 @@ export default class LokiLanguageProvider extends LanguageProvider { hasLogfmt: false, hasPack: false, }; - if (!config.featureToggles.lokiQueryHints) { - return empty; - } const series = await this.datasource.getDataSamples( { diff --git a/public/app/plugins/datasource/loki/LogContextProvider.test.ts b/public/app/plugins/datasource/loki/LogContextProvider.test.ts index dcd86744b47..109003ff15a 100644 --- a/public/app/plugins/datasource/loki/LogContextProvider.test.ts +++ b/public/app/plugins/datasource/loki/LogContextProvider.test.ts @@ -17,7 +17,7 @@ import { LOKI_LOG_CONTEXT_PRESERVED_LABELS, SHOULD_INCLUDE_PIPELINE_OPERATIONS, } from './LogContextProvider'; -import { createLokiDatasource } from './__mocks__/datasource'; +import { createLokiDatasource } from './mocks/datasource'; import { LokiQuery } from './types'; const defaultLanguageProviderMock = { diff --git a/public/app/plugins/datasource/loki/LokiVariableSupport.test.ts b/public/app/plugins/datasource/loki/LokiVariableSupport.test.ts index 0e874d898f5..08697d0f008 100644 --- a/public/app/plugins/datasource/loki/LokiVariableSupport.test.ts +++ b/public/app/plugins/datasource/loki/LokiVariableSupport.test.ts @@ -3,9 +3,9 @@ import { firstValueFrom } from 'rxjs'; import { dateTime, getDefaultTimeRange } from '@grafana/data'; import { LokiVariableSupport } from './LokiVariableSupport'; -import { createLokiDatasource } from './__mocks__/datasource'; -import { createMetadataRequest } from './__mocks__/metadataRequest'; import { LokiDatasource } from './datasource'; +import { createLokiDatasource } from './mocks/datasource'; +import { createMetadataRequest } from './mocks/metadataRequest'; import { LokiVariableQueryType } from './types'; describe('LokiVariableSupport', () => { diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx index 9ab6f7cc667..5897d65018c 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx @@ -5,7 +5,7 @@ import { cloneDeep, defaultsDeep } from 'lodash'; import { CoreApp } from '@grafana/data'; import { QueryEditorMode } from '@grafana/plugin-ui'; -import { createLokiDatasource } from '../__mocks__/datasource'; +import { createLokiDatasource } from '../mocks/datasource'; import { EXPLAIN_LABEL_FILTER_CONTENT } from '../querybuilder/components/LokiQueryBuilderExplained'; import { LokiQuery, LokiQueryType } from '../types'; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx index a7a55a5b709..ef3b3084c18 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx @@ -3,7 +3,7 @@ import { noop } from 'lodash'; import { CoreApp } from '@grafana/data'; -import { createLokiDatasource } from '../__mocks__/datasource'; +import { createLokiDatasource } from '../mocks/datasource'; import { testIds as regularTestIds } from './LokiQueryEditor'; import { LokiQueryEditorByApp } from './LokiQueryEditorByApp'; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx index c9412197892..a7d7e3e0e88 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.test.tsx @@ -3,7 +3,7 @@ import { ComponentProps } from 'react'; import { dateTime } from '@grafana/data'; -import { createLokiDatasource } from '../__mocks__/datasource'; +import { createLokiDatasource } from '../mocks/datasource'; import { LokiQueryField } from './LokiQueryField'; import { Props as MonacoProps } from './monaco-query-field/MonacoQueryFieldProps'; diff --git a/public/app/plugins/datasource/loki/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/VariableQueryEditor.test.tsx index 7644c76bb79..5f251b63a21 100644 --- a/public/app/plugins/datasource/loki/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/VariableQueryEditor.test.tsx @@ -5,7 +5,7 @@ import { select } from 'react-select-event'; import { TimeRange, dateTime } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; -import { createLokiDatasource } from '../__mocks__/datasource'; +import { createLokiDatasource } from '../mocks/datasource'; import { LokiVariableQueryType } from '../types'; import { LokiVariableQueryEditor, Props } from './VariableQueryEditor'; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoFieldWrapper.test.tsx b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoFieldWrapper.test.tsx index ce1aabe2391..0212afd7603 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoFieldWrapper.test.tsx +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoFieldWrapper.test.tsx @@ -2,7 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import { selectors } from '@grafana/e2e-selectors'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { MonacoQueryFieldWrapper, Props } from './MonacoQueryFieldWrapper'; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx index 71b59d08bb1..098232b4a9e 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import { selectors } from '@grafana/e2e-selectors'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import MonacoQueryField from './MonacoQueryField'; import { Props } from './MonacoQueryFieldProps'; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/CompletionDataProvider.test.ts b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/CompletionDataProvider.test.ts index 83e102ad442..d558d774dc1 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/CompletionDataProvider.test.ts +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/CompletionDataProvider.test.ts @@ -1,8 +1,8 @@ import { HistoryItem, dateTime } from '@grafana/data'; import LokiLanguageProvider from '../../../LanguageProvider'; -import { createLokiDatasource } from '../../../__mocks__/datasource'; import { LokiDatasource } from '../../../datasource'; +import { createLokiDatasource } from '../../../mocks/datasource'; import { LokiQuery } from '../../../types'; import { CompletionDataProvider } from './CompletionDataProvider'; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts index de76f3fc915..230a117606a 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts @@ -2,8 +2,8 @@ import { dateTime } from '@grafana/data'; import { Monaco, monacoTypes } from '@grafana/ui'; import LokiLanguageProvider from '../../../LanguageProvider'; -import { createLokiDatasource } from '../../../__mocks__/datasource'; import { LokiDatasource } from '../../../datasource'; +import { createLokiDatasource } from '../../../mocks/datasource'; import { CompletionDataProvider } from './CompletionDataProvider'; import { calculateRange } from './completionUtils'; diff --git a/public/app/plugins/datasource/loki/configuration/AlertingSettings.test.tsx b/public/app/plugins/datasource/loki/configuration/AlertingSettings.test.tsx index 5db292ff3ef..abca10f84dc 100644 --- a/public/app/plugins/datasource/loki/configuration/AlertingSettings.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/AlertingSettings.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createDefaultConfigOptions } from '../__mocks__/datasource'; +import { createDefaultConfigOptions } from '../mocks/datasource'; import { AlertingSettings } from './AlertingSettings'; diff --git a/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx b/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx index 214c68854c0..6524eb4aada 100644 --- a/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createDefaultConfigOptions } from '../__mocks__/datasource'; +import { createDefaultConfigOptions } from '../mocks/datasource'; import { ConfigEditor } from './ConfigEditor'; diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 7c98dbf6dd8..c63332522cd 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -33,9 +33,9 @@ import { } from '@grafana/runtime'; import { LokiVariableSupport } from './LokiVariableSupport'; -import { createLokiDatasource } from './__mocks__/datasource'; -import { createMetadataRequest } from './__mocks__/metadataRequest'; import { LokiDatasource, REF_ID_DATA_SAMPLES } from './datasource'; +import { createLokiDatasource } from './mocks/datasource'; +import { createMetadataRequest } from './mocks/metadataRequest'; import { runSplitQuery } from './querySplitting'; import { LokiOptions, LokiQuery, LokiQueryType, LokiVariableQueryType, SupportingQueryType } from './types'; diff --git a/public/app/plugins/datasource/loki/mergeResponses.test.ts b/public/app/plugins/datasource/loki/mergeResponses.test.ts index 38f3833d6b0..6191a50b731 100644 --- a/public/app/plugins/datasource/loki/mergeResponses.test.ts +++ b/public/app/plugins/datasource/loki/mergeResponses.test.ts @@ -2,8 +2,8 @@ import { cloneDeep } from 'lodash'; import { DataQueryResponse, Field, FieldType, QueryResultMetaStat } from '@grafana/data'; -import { getMockFrames } from './__mocks__/frames'; import { cloneQueryResponse, combineResponses } from './mergeResponses'; +import { getMockFrames } from './mocks/frames'; describe('cloneQueryResponse', () => { const { logFrameA } = getMockFrames(); diff --git a/public/app/plugins/datasource/loki/__mocks__/createDetectedFieldValuesMetadataRequest.ts b/public/app/plugins/datasource/loki/mocks/createDetectedFieldValuesMetadataRequest.ts similarity index 100% rename from public/app/plugins/datasource/loki/__mocks__/createDetectedFieldValuesMetadataRequest.ts rename to public/app/plugins/datasource/loki/mocks/createDetectedFieldValuesMetadataRequest.ts diff --git a/public/app/plugins/datasource/loki/__mocks__/createDetectedFieldsMetadataRequest.ts b/public/app/plugins/datasource/loki/mocks/createDetectedFieldsMetadataRequest.ts similarity index 100% rename from public/app/plugins/datasource/loki/__mocks__/createDetectedFieldsMetadataRequest.ts rename to public/app/plugins/datasource/loki/mocks/createDetectedFieldsMetadataRequest.ts diff --git a/public/app/plugins/datasource/loki/__mocks__/datasource.ts b/public/app/plugins/datasource/loki/mocks/datasource.ts similarity index 100% rename from public/app/plugins/datasource/loki/__mocks__/datasource.ts rename to public/app/plugins/datasource/loki/mocks/datasource.ts diff --git a/public/app/plugins/datasource/loki/__mocks__/frames.ts b/public/app/plugins/datasource/loki/mocks/frames.ts similarity index 100% rename from public/app/plugins/datasource/loki/__mocks__/frames.ts rename to public/app/plugins/datasource/loki/mocks/frames.ts diff --git a/public/app/plugins/datasource/loki/__mocks__/metadataRequest.ts b/public/app/plugins/datasource/loki/mocks/metadataRequest.ts similarity index 100% rename from public/app/plugins/datasource/loki/__mocks__/metadataRequest.ts rename to public/app/plugins/datasource/loki/mocks/metadataRequest.ts diff --git a/public/app/plugins/datasource/loki/querySplitting.test.ts b/public/app/plugins/datasource/loki/querySplitting.test.ts index 9394adf938b..e57c3053c32 100644 --- a/public/app/plugins/datasource/loki/querySplitting.test.ts +++ b/public/app/plugins/datasource/loki/querySplitting.test.ts @@ -3,11 +3,11 @@ import { of } from 'rxjs'; import { DataQueryRequest, dateTime, LoadingState } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { createLokiDatasource } from './__mocks__/datasource'; -import { getMockFrames } from './__mocks__/frames'; import { LokiDatasource } from './datasource'; import * as logsTimeSplit from './logsTimeSplitting'; import * as metricTimeSplit from './metricTimeSplitting'; +import { createLokiDatasource } from './mocks/datasource'; +import { getMockFrames } from './mocks/frames'; import { runSplitQuery } from './querySplitting'; import { trackGroupedQueries } from './tracking'; import { LokiQuery, LokiQueryDirection, LokiQueryType } from './types'; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LabelBrowserModal.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LabelBrowserModal.test.tsx index 6d298cc267e..ba32a7ddac6 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LabelBrowserModal.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LabelBrowserModal.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react'; -import { createLokiDatasource } from '../../__mocks__/datasource'; import { LokiDatasource } from '../../datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiQuery } from '../../types'; import { LabelBrowserModal, Props } from './LabelBrowserModal'; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx index 7095c769d64..6439936934d 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.test.tsx @@ -4,24 +4,15 @@ import { ComponentProps } from 'react'; import { DataSourceApi } from '@grafana/data'; import { QueryBuilderOperation, QueryBuilderOperationParamDef } from '@grafana/plugin-ui'; -import { config } from '@grafana/runtime'; -import { createLokiDatasource } from '../../__mocks__/datasource'; import { LokiDatasource } from '../../datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiQueryModeller } from '../LokiQueryModeller'; import { LokiOperationId } from '../types'; import { LabelParamEditor } from './LabelParamEditor'; describe('LabelParamEditor', () => { - const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints; - beforeAll(() => { - config.featureToggles.lokiQueryHints = true; - }); - afterAll(() => { - config.featureToggles.lokiQueryHints = queryHintsFeatureToggle; - }); - it('shows label options', async () => { const props = createProps({}, ['label1', 'label2']); render(); 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 137d733b7bf..dc6dcbce805 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx @@ -2,9 +2,8 @@ import { render, screen, getAllByRole, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { dateTime } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiOperationId, LokiVisualQuery } from '../types'; import { LokiQueryBuilder, Props, TIME_SPAN_TO_TRIGGER_SAMPLES } from './LokiQueryBuilder'; @@ -40,14 +39,6 @@ const createDefaultProps = () => { }; describe('LokiQueryBuilder', () => { - const originalLokiQueryHints = config.featureToggles.lokiQueryHints; - beforeEach(() => { - config.featureToggles.lokiQueryHints = true; - }); - - afterEach(() => { - config.featureToggles.lokiQueryHints = originalLokiQueryHints; - }); it('tries to load label names', async () => { const props = createDefaultProps(); props.datasource.getDataSamples = jest.fn().mockResolvedValue([]); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx index 96e7a1fa79c..632ac1f7a11 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx @@ -15,7 +15,6 @@ import { QueryBuilderLabelFilter, QueryBuilderOperation, } from '@grafana/plugin-ui'; -import { config } from '@grafana/runtime'; import { testIds } from '../../components/LokiQueryEditor'; import { LokiDatasource } from '../../datasource'; @@ -126,7 +125,7 @@ export const LokiQueryBuilder = memo(({ datasource, query, onChange, onRu (Math.abs(timeRange.to.valueOf() - prevTimeRange.to.valueOf()) > TIME_SPAN_TO_TRIGGER_SAMPLES || Math.abs(timeRange.from.valueOf() - prevTimeRange.from.valueOf()) > TIME_SPAN_TO_TRIGGER_SAMPLES); const updateBasedOnChangedQuery = !isEqual(prevQuery, query); - if (config.featureToggles.lokiQueryHints && (updateBasedOnChangedTimeRange || updateBasedOnChangedQuery)) { + if (updateBasedOnChangedTimeRange || updateBasedOnChangedQuery) { onGetSampleData().catch(console.error); } }, [datasource, query, timeRange, prevQuery, prevTimeRange]); 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 29425688459..ac1ffe809c0 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx @@ -1,7 +1,7 @@ -import { render, screen, waitFor, findAllByRole } from '@testing-library/react'; +import { render, screen, waitFor, findAllByRole, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer'; @@ -19,7 +19,9 @@ describe('LokiQueryBuilderContainer', () => { }; props.datasource.getDataSamples = jest.fn().mockResolvedValue([]); - render(); + await act(async () => { + render(); + }); const selector = await screen.findByLabelText('selector'); expect(selector.textContent).toBe('{job="testjob"}'); await addOperation('Range functions', 'Rate'); @@ -45,7 +47,9 @@ describe('LokiQueryBuilderContainer', () => { props.datasource.languageProvider.fetchLabelValues = jest.fn().mockReturnValue(['grafana', 'loki']); props.onChange = jest.fn(); - render(); + await act(async () => { + render(); + }); await userEvent.click(screen.getByLabelText('Add')); const labels = screen.getByText(/Label filters/); const selects = await findAllByRole(getSelectParent(labels)!, 'combobox'); @@ -78,7 +82,9 @@ describe('LokiQueryBuilderContainer', () => { showExplain: false, }; props.datasource.getDataSamples = jest.fn().mockResolvedValue([]); - render(); + await act(async () => { + render(); + }); expect(screen.getByText('{')).toHaveClass('token punctuation'); expect(screen.getByText('"baz"')).toHaveClass('token label-value attr-value'); expect(screen.getByText('|')).toHaveClass('token pipe-operator operator'); @@ -98,7 +104,9 @@ describe('LokiQueryBuilderContainer', () => { }; props.datasource.getDataSamples = jest.fn().mockResolvedValue([]); - render(); + await act(async () => { + render(); + }); expect(screen.getAllByText('You have conflicting label filters')).toHaveLength(2); }); @@ -115,7 +123,9 @@ describe('LokiQueryBuilderContainer', () => { }; props.datasource.getDataSamples = jest.fn().mockResolvedValue([]); - render(); + await act(async () => { + render(); + }); expect(screen.getByText('<')).toBeInTheDocument(); expect(screen.getByText('expr')).toBeInTheDocument(); expect(screen.getByText('>')).toBeInTheDocument(); 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 b852f1520cd..c03de3eb2e8 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { CoreApp, LogSortOrderChangeEvent, LogsSortOrder, store } from '@grafana/data'; import { config, getAppEvents } from '@grafana/runtime'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiQuery, LokiQueryDirection, LokiQueryType } from '../../types'; import { LokiQueryBuilderOptions, Props } from './LokiQueryBuilderOptions'; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.test.tsx index 305e38c24d5..729846fdab8 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.test.tsx @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'; import { selectors } from '@grafana/e2e-selectors'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiQuery } from '../../types'; import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained'; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx index 768560002e3..95f5c5f1f39 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createLokiDatasource } from '../../__mocks__/datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiVisualQueryBinary } from '../types'; import { NestedQuery, Props as NestedQueryProps } from './NestedQuery'; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/NestedQueryList.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/NestedQueryList.test.tsx index ce281f06b43..b0c2782e4cf 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/NestedQueryList.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/NestedQueryList.test.tsx @@ -1,8 +1,8 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { createLokiDatasource } from '../../__mocks__/datasource'; import { LokiDatasource } from '../../datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiVisualQuery, LokiVisualQueryBinary } from '../types'; import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained'; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx index bf69d3587fb..f21d37b2544 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx @@ -4,24 +4,15 @@ import { ComponentProps } from 'react'; import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data'; import { QueryBuilderOperation, QueryBuilderOperationParamDef } from '@grafana/plugin-ui'; -import { config } from '@grafana/runtime'; -import { createLokiDatasource } from '../../__mocks__/datasource'; import { LokiDatasource } from '../../datasource'; +import { createLokiDatasource } from '../../mocks/datasource'; import { LokiQueryModeller } from '../LokiQueryModeller'; import { LokiOperationId } from '../types'; import { UnwrapParamEditor } from './UnwrapParamEditor'; describe('UnwrapParamEditor', () => { - const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints; - beforeAll(() => { - config.featureToggles.lokiQueryHints = true; - }); - afterAll(() => { - config.featureToggles.lokiQueryHints = queryHintsFeatureToggle; - }); - it('shows value if value present', () => { const props = createProps({ value: 'unique' }); render(); @@ -60,16 +51,6 @@ describe('UnwrapParamEditor', () => { expect(await screen.findByText('status')).toBeInTheDocument(); expect(await screen.findByText('duration')).toBeInTheDocument(); }); - - it('does not show labels with unwrap-friendly values when feature is disabled', async () => { - config.featureToggles.lokiQueryHints = false; - const props = createProps({}, frames); - render(); - const input = screen.getByRole('combobox'); - await userEvent.click(input); - expect(screen.queryByText('status')).not.toBeInTheDocument(); - expect(screen.queryByText('duration')).not.toBeInTheDocument(); - }); }); const createProps = ( diff --git a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx index 630cb285568..bfb8306b6f0 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.tsx @@ -2,7 +2,6 @@ import { useState } from 'react'; import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data'; import { QueryBuilderOperationParamEditorProps, VisualQueryModeller } from '@grafana/plugin-ui'; -import { config } from '@grafana/runtime'; import { Select } from '@grafana/ui'; import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation'; @@ -32,7 +31,7 @@ export function UnwrapParamEditor({ inputId={getOperationParamId(operationId, index)} onOpenMenu={async () => { // This check is always true, we do it to make typescript happy - if (datasource instanceof LokiDatasource && config.featureToggles.lokiQueryHints) { + if (datasource instanceof LokiDatasource) { setState({ isLoading: true }); const options = await loadUnwrapOptions(query, datasource, queryModeller, timeRange); setState({ options, isLoading: undefined }); diff --git a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts index 51b8bb308ee..566eddeae81 100644 --- a/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts +++ b/public/app/plugins/datasource/loki/shardQuerySplitting.test.ts @@ -2,9 +2,9 @@ import { of } from 'rxjs'; import { DataQueryRequest, DataQueryResponse, dateTime, LoadingState } from '@grafana/data'; -import { createLokiDatasource } from './__mocks__/datasource'; -import { getMockFrames } from './__mocks__/frames'; import { LokiDatasource } from './datasource'; +import { createLokiDatasource } from './mocks/datasource'; +import { getMockFrames } from './mocks/frames'; import { runShardSplitQuery } from './shardQuerySplitting'; import { LokiQuery, LokiQueryDirection } from './types'; diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 75a6d2d9955..6a1e7dbaab0 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -110,6 +110,12 @@ "update-button": "Aktualizovat", "users-heading": "Uživatelé organizace" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Provádění auditu", "title-custom-branding": "Vlastní branding", @@ -3503,18 +3509,12 @@ "button-create": "Vytvořit", "button-creating": "Vytváření…", "cancel": "Zrušit", - "description-branch-name-in-git-hub": "Název větvě v GitHubu", "error-invalid-folder-name": "Neplatný název složky", "error-required": "Název složky je povinný", - "folder-comment-input-placeholder-describe-changes-optional": "Přidat poznámku k popisu změn (nepovinné)", "folder-name-input-placeholder-enter-folder-name": "Zadejte název složky", - "label-branch": "Větev", - "label-comment": "Komentář", "label-folder-name": "Název složky", - "label-workflow": "Pracovní tok", "text-pull-request-created": "Byl vytvořen pull request se změnami v této složce:", "title-pull-request-created": "Pull request byl vytvořen", - "title-repository-not-found": "Úložiště nebylo nalezeno", "title-this-repository-is-read-only": "Toto úložiště je jen pro čtení" }, "no-results": { @@ -12143,6 +12143,7 @@ "polynomial": "Polynomický" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Počet bodů X, Y k předpovědi" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 74e56c487bf..5ce02f8a0d8 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -110,6 +110,12 @@ "update-button": "Aktualisieren", "users-heading": "Benutzer in der Organisation" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditing", "title-custom-branding": "Individuelles Branding", @@ -3467,18 +3473,12 @@ "button-create": "Erstellen", "button-creating": "Wird erstellt ...", "cancel": "Abbrechen", - "description-branch-name-in-git-hub": "Branch-Name in GitHub", "error-invalid-folder-name": "Ungültiger Ordnername", "error-required": "Ordnername erforderlich", - "folder-comment-input-placeholder-describe-changes-optional": "Fügen Sie eine Notiz hinzu, um Ihre Änderungen zu beschreiben (optional)", "folder-name-input-placeholder-enter-folder-name": "Ordnernamen eingeben", - "label-branch": "Branch", - "label-comment": "Kommentar", "label-folder-name": "Ordnername", - "label-workflow": "Workflow", "text-pull-request-created": "Ein Pull-Request wurde erstellt, mit Änderungen an diesem Ordner:", "title-pull-request-created": "Pull-Request erstellt", - "title-repository-not-found": "Repository nicht gefunden", "title-this-repository-is-read-only": "Dieses Repository ist schreibgeschützt" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polynom" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Anzahl der zu prognostizierenden X-, Y-Punkte" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 8d0e4371590..fbdb3450d6e 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -110,6 +110,12 @@ "update-button": "Actualizar", "users-heading": "Usuarios de la organización" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditoría", "title-custom-branding": "Marca personalizada", @@ -3467,18 +3473,12 @@ "button-create": "Crear", "button-creating": "Creando...", "cancel": "Cancelar", - "description-branch-name-in-git-hub": "Nombre de la rama en GitHub", "error-invalid-folder-name": "Nombre de carpeta no válido", "error-required": "Nombre de la carpeta obligatorio", - "folder-comment-input-placeholder-describe-changes-optional": "Añade una nota para describir tus cambios (opcional)", "folder-name-input-placeholder-enter-folder-name": "Introducir el nombre de la carpeta", - "label-branch": "Rama", - "label-comment": "Comentario", "label-folder-name": "Nombre de la carpeta", - "label-workflow": "Flujo de trabajo", "text-pull-request-created": "Se ha creado una solicitud de extracción con cambios en esta carpeta:", "title-pull-request-created": "Solicitud de extracción creada", - "title-repository-not-found": "Repositorio no encontrado", "title-this-repository-is-read-only": "Este repositorio es de solo lectura" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polinómica" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Número de puntos X,Y para predecir" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 15a379c1779..a1254bb741a 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -110,6 +110,12 @@ "update-button": "Mettre à jour", "users-heading": "Utilisateurs de l'organisation" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditer", "title-custom-branding": "Marquage personnalisé", @@ -3467,18 +3473,12 @@ "button-create": "Créer", "button-creating": "Création...", "cancel": "Annuler", - "description-branch-name-in-git-hub": "Nom de la branche dans GitHub", "error-invalid-folder-name": "Nom de dossier non valide", "error-required": "Un nom de dossier est obligatoire", - "folder-comment-input-placeholder-describe-changes-optional": "Ajoutez une note pour décrire vos modifications (facultatif)", "folder-name-input-placeholder-enter-folder-name": "Saisir le nom du dossier", - "label-branch": "Branche", - "label-comment": "Commentaire", "label-folder-name": "Nom du dossier", - "label-workflow": "Flux de travail", "text-pull-request-created": "Une demande de fusion a été créée avec des modifications dans ce dossier :", "title-pull-request-created": "Demande de fusion créée", - "title-repository-not-found": "Référentiel introuvable", "title-this-repository-is-read-only": "Ce référentiel est en lecture seule" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polynomial" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Nombre de points X, Y à prédire" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 608370c9054..cc9c3a2967b 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -110,6 +110,12 @@ "update-button": "Frissítés", "users-heading": "Szervezeti felhasználók" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditálás", "title-custom-branding": "Saját márkajelzés használata", @@ -3467,18 +3473,12 @@ "button-create": "Létrehozás", "button-creating": "Létrehozás…", "cancel": "Mégse", - "description-branch-name-in-git-hub": "Ág neve a GitHubon", "error-invalid-folder-name": "Érvénytelen mappanév", "error-required": "A mappa nevének megadása kötelező", - "folder-comment-input-placeholder-describe-changes-optional": "Megjegyzés hozzáadása a módosítások leírásához (nem kötelező)", "folder-name-input-placeholder-enter-folder-name": "Mappanév megadása", - "label-branch": "Ág", - "label-comment": "Megjegyzés", "label-folder-name": "Mappa neve", - "label-workflow": "Munkafolyamat", "text-pull-request-created": "A lekérési kérelem ennek a mappának a módosításával lett létrehozva:", "title-pull-request-created": "Lekérési kérelem létrehozva", - "title-repository-not-found": "Nem található adattár", "title-this-repository-is-read-only": "Ez az adattár csak olvasható" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polinomiális" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Előre jelzendő X,Y pontok száma" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index dcf3fe6bf50..f63f0f6b315 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -110,6 +110,12 @@ "update-button": "Perbarui", "users-heading": "Pengguna organisasi" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Audit", "title-custom-branding": "Branding kustom", @@ -3449,18 +3455,12 @@ "button-create": "Buat", "button-creating": "Membuat...", "cancel": "Batalkan", - "description-branch-name-in-git-hub": "Nama cabang di GitHub", "error-invalid-folder-name": "Nama folder tidak valid", "error-required": "Nama folder wajib diisi", - "folder-comment-input-placeholder-describe-changes-optional": "Tambahkan catatan untuk menjelaskan perubahan Anda (opsional)", "folder-name-input-placeholder-enter-folder-name": "Masukkan nama folder", - "label-branch": "Cabang", - "label-comment": "Komentar", "label-folder-name": "Nama folder", - "label-workflow": "Alur kerja", "text-pull-request-created": "Permintaan pull telah dibuat dengan perubahan pada folder ini:", "title-pull-request-created": "Permintaan pull dibuat", - "title-repository-not-found": "Repositori tidak ditemukan", "title-this-repository-is-read-only": "Repositori ini hanya dapat dibaca" }, "no-results": { @@ -12038,6 +12038,7 @@ "polynomial": "Polinomial" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Jumlah poin X,Y untuk diprediksi" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index d1d5b36a13b..03f35d3bbc5 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -110,6 +110,12 @@ "update-button": "Aggiorna", "users-heading": "Utenti dell'organizzazione" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Verifiche", "title-custom-branding": "Marchio personalizzato", @@ -3467,18 +3473,12 @@ "button-create": "Crea", "button-creating": "Creazione in corso...", "cancel": "Annulla", - "description-branch-name-in-git-hub": "Nome del ramo in GitHub", "error-invalid-folder-name": "Nome cartella non valido", "error-required": "Il nome della cartella è obbligatorio", - "folder-comment-input-placeholder-describe-changes-optional": "Aggiungi una nota per descrivere le modifiche (facoltativo)", "folder-name-input-placeholder-enter-folder-name": "Inserisci nome cartella", - "label-branch": "Ramo", - "label-comment": "Commento", "label-folder-name": "Nome cartella", - "label-workflow": "Flusso di lavoro", "text-pull-request-created": "È stata creata una richiesta di pull con modifiche a questa cartella:", "title-pull-request-created": "Richiesta di pull creata", - "title-repository-not-found": "Repository non trovato", "title-this-repository-is-read-only": "Questo repository è di sola lettura" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Numero di punti X, Y da prevedere" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 7d191bd8446..47a76879ee1 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -110,6 +110,12 @@ "update-button": "アップデート", "users-heading": "組織ユーザー" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "監査", "title-custom-branding": "カスタムブランディング", @@ -3449,18 +3455,12 @@ "button-create": "作成", "button-creating": "作成中...", "cancel": "キャンセル", - "description-branch-name-in-git-hub": "GitHubのブランチ名", "error-invalid-folder-name": "無効なフォルダ名", "error-required": "フォルダ名は必須です", - "folder-comment-input-placeholder-describe-changes-optional": "変更内容を説明するメモを追加します(オプション)", "folder-name-input-placeholder-enter-folder-name": "フォルダ名を入力してください", - "label-branch": "ブランチ", - "label-comment": "コメント", "label-folder-name": "フォルダ名", - "label-workflow": "ワークフロー", "text-pull-request-created": "このフォルダへの変更を含むプルリクエストが作成されました。", "title-pull-request-created": "プルリクエストが作成されました", - "title-repository-not-found": "リポジトリが見つかりません", "title-this-repository-is-read-only": "このリポジトリは読み取り専用です" }, "no-results": { @@ -12038,6 +12038,7 @@ "polynomial": "多項式" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "予測するX、Yポイント数" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 468eb141de0..579cbc69bb3 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -110,6 +110,12 @@ "update-button": "업데이트", "users-heading": "조직 사용자" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "감사", "title-custom-branding": "사용자 지정 브랜딩", @@ -3449,18 +3455,12 @@ "button-create": "생성", "button-creating": "생성 중...", "cancel": "취소", - "description-branch-name-in-git-hub": "GitHub의 브랜치 이름", "error-invalid-folder-name": "유효하지 않은 폴더 이름", "error-required": "폴더 이름은 필수 입력 항목입니다", - "folder-comment-input-placeholder-describe-changes-optional": "변경 사항을 설명하는 메모 추가(선택 사항)", "folder-name-input-placeholder-enter-folder-name": "폴더 이름 입력", - "label-branch": "브랜치", - "label-comment": "코멘트", "label-folder-name": "폴더 이름", - "label-workflow": "워크플로", "text-pull-request-created": "이 폴더에 대한 변경 사항을 포함하는 풀 요청이 생성되었습니다.", "title-pull-request-created": "풀 요청 생성됨", - "title-repository-not-found": "리포지토리를 찾을 수 없습니다", "title-this-repository-is-read-only": "이 리포지토리는 읽기 전용입니다." }, "no-results": { @@ -12038,6 +12038,7 @@ "polynomial": "다항식" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "예측할 X, Y 포인트 수" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index a0415ddd4b3..a29386de438 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -110,6 +110,12 @@ "update-button": "Bijwerken", "users-heading": "Gebruikers van de organisatie" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditing", "title-custom-branding": "Aangepaste branding", @@ -3467,18 +3473,12 @@ "button-create": "Aanmaken", "button-creating": "Aanmaken...", "cancel": "Annuleren", - "description-branch-name-in-git-hub": "Filiaalnaam in GitHub", "error-invalid-folder-name": "Ongeldige mapnaam", "error-required": "Mapnaam is vereist", - "folder-comment-input-placeholder-describe-changes-optional": "Een opmerking toevoegen om je wijzigingen te beschrijven (optioneel)", "folder-name-input-placeholder-enter-folder-name": "Mapnaam invoeren", - "label-branch": "Vestiging", - "label-comment": "Opmerking", "label-folder-name": "Mapnaam", - "label-workflow": "Werkstroom", "text-pull-request-created": "Er is een pull request gemaakt met wijzigingen in deze map:", "title-pull-request-created": "Pull request is aangemaakt", - "title-repository-not-found": "Repository niet gevonden", "title-this-repository-is-read-only": "Deze repository is alleen-lezen" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polynoom" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Aantal X-,Y-punten om te voorspellen" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 37ea79860a5..4b87289505b 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -110,6 +110,12 @@ "update-button": "Aktualizuj", "users-heading": "Użytkownicy organizacji" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Audyty", "title-custom-branding": "Własny branding", @@ -3503,18 +3509,12 @@ "button-create": "Utwórz", "button-creating": "Tworzenie…", "cancel": "Anuluj", - "description-branch-name-in-git-hub": "Nazwa gałęzi w GitHub", "error-invalid-folder-name": "Nieprawidłowa nazwa folderu", "error-required": "Wymagana jest nazwa folderu", - "folder-comment-input-placeholder-describe-changes-optional": "Dodaj notatkę, aby opisać zmiany (opcjonalnie)", "folder-name-input-placeholder-enter-folder-name": "Podaj nazwę folderu", - "label-branch": "Gałąź", - "label-comment": "Uwagi", "label-folder-name": "Nazwa folderu", - "label-workflow": "Przepływ pracy", "text-pull-request-created": "Utworzono żądanie pull ze zmianami w tym folderze:", "title-pull-request-created": "Utworzono żądanie pull", - "title-repository-not-found": "Nie znaleziono repozytorium", "title-this-repository-is-read-only": "To repozytorium jest tylko do odczytu" }, "no-results": { @@ -12143,6 +12143,7 @@ "polynomial": "Wielomian" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Liczba punktów X,Y do prognozowania" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 93825952810..4a068661cc7 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -110,6 +110,12 @@ "update-button": "Atualizar", "users-heading": "Usuários da organização" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditoria", "title-custom-branding": "Branding personalizado", @@ -3467,18 +3473,12 @@ "button-create": "Criar", "button-creating": "Criando…", "cancel": "Cancelar", - "description-branch-name-in-git-hub": "Nome do branch no GitHub", "error-invalid-folder-name": "Nome da pasta inválido", "error-required": "O nome da pasta é obrigatório", - "folder-comment-input-placeholder-describe-changes-optional": "Adicione uma nota para descrever suas alterações (opcional)", "folder-name-input-placeholder-enter-folder-name": "Digitar o nome da pasta", - "label-branch": "Branch", - "label-comment": "Comentário", "label-folder-name": "Nome da pasta", - "label-workflow": "Fluxo de trabalho", "text-pull-request-created": "Uma solicitação de extração foi criada com alterações nesta pasta:", "title-pull-request-created": "Solicitação de extração criada", - "title-repository-not-found": "Repositório não encontrado", "title-this-repository-is-read-only": "Este repositório é somente leitura" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polinomial" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Número de pontos X,Y a serem previstos" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 6cc1baff957..b6aa70bb2b0 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -110,6 +110,12 @@ "update-button": "Atualizar", "users-heading": "Utilizadores da organização" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Auditoria", "title-custom-branding": "Marca personalizada", @@ -3467,18 +3473,12 @@ "button-create": "Criar", "button-creating": "A criar...", "cancel": "Cancelar", - "description-branch-name-in-git-hub": "Nome do ramo no GitHub", "error-invalid-folder-name": "Nome da pasta inválido", "error-required": "O nome da pasta é obrigatório", - "folder-comment-input-placeholder-describe-changes-optional": "Adicione uma nota para descrever as suas alterações (opcional)", "folder-name-input-placeholder-enter-folder-name": "Introduza o nome da pasta", - "label-branch": "Ramo", - "label-comment": "Comentário", "label-folder-name": "Nome da pasta", - "label-workflow": "Fluxo de trabalho", "text-pull-request-created": "Foi criado um pedido de extração com alterações a esta pasta:", "title-pull-request-created": "Pedido de extração criado", - "title-repository-not-found": "Repositório não encontrado", "title-this-repository-is-read-only": "Este repositório é apenas de leitura" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polinómio" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Número de pontos X, Y para prever" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 51250326a1c..a38ed0702b1 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -110,6 +110,12 @@ "update-button": "Обновить", "users-heading": "Пользователи организации" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Проверка", "title-custom-branding": "Пользовательский брендинг", @@ -3503,18 +3509,12 @@ "button-create": "Создание", "button-creating": "Создание...", "cancel": "Отмена", - "description-branch-name-in-git-hub": "Имя ветви в GitHub", "error-invalid-folder-name": "Недопустимое имя папки", "error-required": "Требуется имя папки", - "folder-comment-input-placeholder-describe-changes-optional": "Добавьте примечание с описанием изменений (необязательно)", "folder-name-input-placeholder-enter-folder-name": "Ввести имя папки", - "label-branch": "Ветвь", - "label-comment": "Комментарий", "label-folder-name": "Имя папки", - "label-workflow": "Рабочий процесс", "text-pull-request-created": "Создан запрос на включение изменений в папке:", "title-pull-request-created": "Создан запрос на включение изменений", - "title-repository-not-found": "Репозиторий не найден", "title-this-repository-is-read-only": "Репозиторий предназначен только для чтения" }, "no-results": { @@ -12143,6 +12143,7 @@ "polynomial": "Полиномиальная" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Количество точек X, Y для прогнозирования" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 3338baff813..55114795598 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -110,6 +110,12 @@ "update-button": "Uppdatera", "users-heading": "Organisationsanvändare" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Revision", "title-custom-branding": "Anpassad marknadsföring", @@ -3467,18 +3473,12 @@ "button-create": "Skapa", "button-creating": "Skapar …", "cancel": "Avbryt", - "description-branch-name-in-git-hub": "Grennamn i GitHub", "error-invalid-folder-name": "Ogiltigt mappnamn", "error-required": "Mappnamn krävs", - "folder-comment-input-placeholder-describe-changes-optional": "Lägg till en anteckning för att beskriva dina ändringar (valfritt)", "folder-name-input-placeholder-enter-folder-name": "Ange mappnamn", - "label-branch": "Gren", - "label-comment": "Kommentar", "label-folder-name": "Mappnamn", - "label-workflow": "Arbetsflöde", "text-pull-request-created": "En pull-begäran har skapats med ändringar i den här mappen:", "title-pull-request-created": "Pull-begäran skapad", - "title-repository-not-found": "Lagringsplatsen hittades inte", "title-this-repository-is-read-only": "Den här databasen är skrivskyddad" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polynom" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Antal X,Y-poäng att förutsäga" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 00ee926c8dc..af48be053be 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -110,6 +110,12 @@ "update-button": "Güncelle", "users-heading": "Kuruluş kullanıcıları" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "Denetim", "title-custom-branding": "Özel markalama", @@ -3467,18 +3473,12 @@ "button-create": "Oluştur", "button-creating": "Oluşturuluyor...", "cancel": "İptal", - "description-branch-name-in-git-hub": "GitHub'daki dal adı", "error-invalid-folder-name": "Geçersiz klasör adı", "error-required": "Klasör adı gereklidir", - "folder-comment-input-placeholder-describe-changes-optional": "Değişikliklerinizi açıklayan bir not ekleyin (isteğe bağlı)", "folder-name-input-placeholder-enter-folder-name": "Klasör adını girin", - "label-branch": "Dal", - "label-comment": "Yorum", "label-folder-name": "Klasör adı", - "label-workflow": "İş akışı", "text-pull-request-created": "Bu klasörde yapılan değişikliklerle bir çekme isteği oluşturuldu:", "title-pull-request-created": "Çekme isteği oluşturuldu", - "title-repository-not-found": "Depo bulunamadı", "title-this-repository-is-read-only": "Bu depo salt okunur" }, "no-results": { @@ -12073,6 +12073,7 @@ "polynomial": "Polinom" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "Tahmin edilecek X,Y noktalarının sayısı" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 2ef9077b914..1e1b0aa3b0e 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -110,6 +110,12 @@ "update-button": "更新", "users-heading": "组织用户" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "审计", "title-custom-branding": "定制品牌", @@ -3449,18 +3455,12 @@ "button-create": "创建", "button-creating": "正在创建...", "cancel": "取消", - "description-branch-name-in-git-hub": "GitHub 中的分支名称", "error-invalid-folder-name": "文件夹名称无效", "error-required": "文件夹名称为必填项", - "folder-comment-input-placeholder-describe-changes-optional": "添加备注以描述您的更改(可选)", "folder-name-input-placeholder-enter-folder-name": "输入文件夹名称", - "label-branch": "分支", - "label-comment": "评论", "label-folder-name": "文件夹名称", - "label-workflow": "工作流程", "text-pull-request-created": "已创建拉取请求,对此文件夹进行更改:", "title-pull-request-created": "拉取请求已创建", - "title-repository-not-found": "找不到存储库", "title-this-repository-is-read-only": "此存储库为只读" }, "no-results": { @@ -12038,6 +12038,7 @@ "polynomial": "多项式" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "要预测的 X,Y 点的数量" }, "rename-by-regex-transformer-editor": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 52fec81b959..826ce308fed 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -110,6 +110,12 @@ "update-button": "更新", "users-heading": "組織使用者" }, + "enterprise-auth-features-card": { + "dismiss": "", + "heading": "", + "learn-more-link": "", + "text": "" + }, "feature-listing": { "title-auditing": "稽核", "title-custom-branding": "自訂品牌", @@ -3449,18 +3455,12 @@ "button-create": "建立", "button-creating": "正在建立…", "cancel": "取消", - "description-branch-name-in-git-hub": "GitHub 中的分支名稱", "error-invalid-folder-name": "無效的資料夾名稱", "error-required": "資料夾名稱為必填", - "folder-comment-input-placeholder-describe-changes-optional": "新增備註以描述您的變更(選填)", "folder-name-input-placeholder-enter-folder-name": "輸入資料夾名稱", - "label-branch": "分支", - "label-comment": "評論", "label-folder-name": "資料夾名稱", - "label-workflow": "工作流程", "text-pull-request-created": "已建立拉取請求,其中包含對此資料夾的變更:", "title-pull-request-created": "已建立拉取請求", - "title-repository-not-found": "找不到儲存庫", "title-this-repository-is-read-only": "此存放庫為唯讀" }, "no-results": { @@ -12038,6 +12038,7 @@ "polynomial": "多項式" } }, + "regression": "", "tooltip-number-of-xy-points-to-predict": "要預測的 X、Y 點數" }, "rename-by-regex-transformer-editor": { diff --git a/yarn.lock b/yarn.lock index 977c1b5cb1e..9439036c398 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3542,11 +3542,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.24.0": - version: 6.25.0 - resolution: "@grafana/scenes-react@npm:6.25.0" +"@grafana/scenes-react@npm:^6.26.1": + version: 6.26.1 + resolution: "@grafana/scenes-react@npm:6.26.1" dependencies: - "@grafana/scenes": "npm:6.25.0" + "@grafana/scenes": "npm:6.26.1" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3558,13 +3558,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/3064b8270098f4489239051741ccc4edb73352f91687fe14037751593363526164ad737e51e6820a850eba436f04454d0b8d7be14fbdb0e403161045d39f6064 + checksum: 10/65ab66edcc19cdd9e05071317885688106c09c72ad6ada1429a3d3bdc0605814493e3a428af9178f90f13f620e10169280c33bbc63fa6c0dd6c40b81378cc547 languageName: node linkType: hard -"@grafana/scenes@npm:6.25.0, @grafana/scenes@npm:^6.24.0": - version: 6.25.0 - resolution: "@grafana/scenes@npm:6.25.0" +"@grafana/scenes@npm:6.26.1, @grafana/scenes@npm:^6.26.1": + version: 6.26.1 + resolution: "@grafana/scenes@npm:6.26.1" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3584,7 +3584,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/eae7ccb82fad458dffbc71fa8b8273e8a57686cd95c0aa76929df51b81eca43117c3d04e31a279d87fd295672df98b0ee28c3501377518c73f6c935a1a791f20 + checksum: 10/aed5a13a17fe8ed172637cd75edae9d20815321d2145ba0b4a179950969b6a1bcbe732ac42312b7ef34e7454d18725fd23ca58934fad43ce77b1edc2efd62ef7 languageName: node linkType: hard @@ -18202,8 +18202,8 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.7" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.24.0" - "@grafana/scenes-react": "npm:^6.24.0" + "@grafana/scenes": "npm:^6.26.1" + "@grafana/scenes-react": "npm:^6.26.1" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*"