merge main

This commit is contained in:
Ryan McKinley
2025-08-22 20:05:53 +03:00
378 changed files with 6303 additions and 9970 deletions
+9 -15
View File
@@ -6,18 +6,20 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y');
const lodashPlugin = require('eslint-plugin-lodash');
const barrelPlugin = require('eslint-plugin-no-barrel-files');
const reactPlugin = require('eslint-plugin-react');
const hooksPlugin = require('eslint-plugin-react-hooks');
const testingLibraryPlugin = require('eslint-plugin-testing-library');
const grafanaConfig = require('@grafana/eslint-config/flat');
const grafanaPlugin = require('@grafana/eslint-plugin');
const grafanaI18nPlugin = require('@grafana/i18n/eslint-plugin');
// Include the Grafana config and remove the rules,
// Include the base Grafana configs and remove the rules,
// as we just want to pull in all of the necessary configuration but not run the rules
// (this should only be concerned with checking rules that we want to improve,
// so there's no need to try and run the rules that will be linted properly anyway)
const { rules, ...baseConfig } = grafanaConfig.find((config) => config?.name === '@grafana/eslint-config/flat');
const mappedBaseConfigs = grafanaConfig.map((config) => {
const { rules, ...baseConfig } = config;
return baseConfig;
});
/**
* @type {Array<import('eslint').Linter.Config>}
@@ -50,22 +52,13 @@ module.exports = [
],
},
{
name: 'react/jsx-runtime',
// @ts-ignore - not sure why but flat config is typed as a maybe?
...reactPlugin.configs.flat['jsx-runtime'],
},
// FIXME: Remove once eslint-config-grafana is updated to include correct plugin
{
name: 'react-hooks-plugin',
plugins: {
'react-hooks': hooksPlugin,
},
name: 'react/jsx-runtime-rules',
rules: reactPlugin.configs.flat['jsx-runtime'].rules,
},
...mappedBaseConfigs,
{
files: ['**/*.{ts,tsx,js}'],
...baseConfig,
plugins: {
...baseConfig.plugins,
'@emotion': emotionPlugin,
lodash: lodashPlugin,
jest: jestPlugin,
@@ -85,6 +78,7 @@ module.exports = [
{
files: ['**/*.{js,jsx,ts,tsx}'],
rules: {
'react-hooks/rules-of-hooks': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@grafana/no-aria-label-selectors': 'error',
'no-restricted-imports': [
+172 -8
View File
@@ -548,6 +548,16 @@ exports[`better eslint`] = {
"packages/grafana-runtime/src/utils/queryResponse.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-runtime/src/utils/useFavoriteDatasources.ts:5381": [
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "2"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "3"],
[0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "4"],
[0, 0, 0, "React Hook \\"useState\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "5"],
[0, 0, 0, "React Hook \\"useState\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "6"],
[0, 0, 0, "React Hook \\"useState\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "7"]
],
"packages/grafana-runtime/src/utils/userStorage.tsx:5381": [
[0, 0, 0, "Direct usage of localStorage is not allowed. import store from @grafana/data instead", "0"],
[0, 0, 0, "Direct usage of localStorage is not allowed. import store from @grafana/data instead", "1"],
@@ -596,6 +606,20 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"packages/grafana-ui/src/components/Combobox/Combobox.story.tsx:5381": [
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"],
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"],
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"],
[0, 0, 0, "React Hook \\"useEffect\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"],
[0, 0, 0, "React Hook \\"useState\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"]
],
"packages/grafana-ui/src/components/Combobox/MultiCombobox.story.tsx:5381": [
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"],
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"],
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"],
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"],
[0, 0, 0, "React Hook \\"useArgs\\" is called in function \\"render\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"]
],
"packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
@@ -616,6 +640,33 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"packages/grafana-ui/src/components/ElementSelectionContext/ElementSelectionContext.tsx:5381": [
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"],
[0, 0, 0, "React Hook \\"useContext\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"]
],
"packages/grafana-ui/src/components/Forms/Form.story.tsx:5381": [
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "5"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "6"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "7"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"renderForm\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "8"]
],
"packages/grafana-ui/src/components/Forms/InlineField.story.tsx:5381": [
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"basic: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"error: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"grow: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "2"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"multiple: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "3"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"multiple: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "4"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"multiple: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "5"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"withCombobox: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "6"],
[0, 0, 0, "React Hook \\"useId\\" is called in function \\"withTooltip: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "7"],
[0, 0, 0, "React Hook \\"useState\\" is called in function \\"withCombobox: StoryFn<typeof InlineField>\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "8"]
],
"packages/grafana-ui/src/components/Forms/Legacy/Input/Input.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
@@ -864,7 +915,10 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/api/clients/folder/v1beta1/hooks.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "React Hook \\"useGetDisplayMappingQuery\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"],
[0, 0, 0, "React Hook \\"useGetFolderParentsQuery\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "2"],
[0, 0, 0, "React Hook \\"useGetFolderQuery\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "3"]
],
"public/app/core/TableModel.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@@ -1274,6 +1328,9 @@ 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.", "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"]
],
"public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx:5381": [
[0, 0, 0, "React Hook \\"useStyles2\\" is called in function \\"getPreviewResults\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"]
],
"public/app/features/alerting/unified/components/receivers/form/ChannelOptions.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, "Do not use any type assertions.", "1"],
@@ -1403,6 +1460,9 @@ exports[`better eslint`] = {
"public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.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"]
],
"public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx:5381": [
[0, 0, 0, "React Hook \\"useStyles2\\" is called in function \\"createMetadata\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"]
],
"public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.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"],
@@ -1554,6 +1614,18 @@ exports[`better eslint`] = {
"public/app/features/browse-dashboards/components/NewFolderForm.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"]
],
"public/app/features/commandPalette/actions/recentScopesActions.ts:5381": [
[0, 0, 0, "React Hook \\"useScopesServices\\" is called in function \\"getRecentScopesActions\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"]
],
"public/app/features/commandPalette/actions/scopeActions.tsx:5381": [
[0, 0, 0, "React Hook \\"useGlobalScopesSearch\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"],
[0, 0, 0, "React Hook \\"useRegisterActions\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"],
[0, 0, 0, "React Hook \\"useScopeTreeActions\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"],
[0, 0, 0, "React Hook \\"useScopesRow\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "3"]
],
"public/app/features/commandPalette/actions/scopesUtils.ts:5381": [
[0, 0, 0, "React Hook \\"useObservable\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"]
],
"public/app/features/connections/components/ConnectionsRedirectNotice/index.ts:5381": [
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"]
],
@@ -1633,9 +1705,23 @@ exports[`better eslint`] = {
"public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx:5381": [
[0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"],
[0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"],
[0, 0, 0, "React Hook \\"useSceneObjectState\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"],
[0, 0, 0, "React Hook \\"useSnappingSplitter\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "3"]
],
"public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx:5381": [
[0, 0, 0, "React Hook \\"useLayoutCategory\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"],
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx:5381": [
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"],
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.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"],
@@ -1645,7 +1731,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/dashboard-scene/inspect/InspectJsonTab.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.", "0"],
[0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/pages/DashboardScenePage.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
@@ -1662,7 +1749,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.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.", "0"],
[0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"],
[0, 0, 0, "React Hook \\"useToggle\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"]
],
"public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.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"]
@@ -1673,6 +1762,9 @@ 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.", "2"],
[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.", "3"]
],
"public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx:5381": [
[0, 0, 0, "React Hook \\"useIsProvisionedNG\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
],
"public/app/features/dashboard-scene/saving/SaveDashboardForm.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"]
],
@@ -1712,21 +1804,39 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"]
],
"public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridItemEditor.tsx:5381": [
[0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" is called in function \\"getOptions\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"]
],
"public/app/features/dashboard-scene/scene/layout-auto-grid/AutoGridLayoutManagerEditor.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"],
[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"],
[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.", "3"]
],
"public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx:5381": [
[0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" is called in function \\"getDashboardGridItemOptions\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"]
],
"public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx:5381": [
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"],
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/scene/layout-default/row-actions/RowOptionsForm.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"]
],
"public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx:5381": [
[0, 0, 0, "React Hook \\"useEditOptions\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
],
"public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.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.", "0"],
[0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/scene/layout-tabs/TabItem.tsx:5381": [
[0, 0, 0, "React Hook \\"useEditOptions\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
],
"public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.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.", "0"],
[0, 0, 0, "React Hook \\"useConditionalRenderingEditor\\" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/serialization/angularMigration.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@@ -1751,6 +1861,15 @@ exports[`better eslint`] = {
"public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx:5381": [
[0, 0, 0, "React Hook \\"useDashboardEditPageNav\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
],
"public/app/features/dashboard-scene/settings/JsonModelEditView.tsx:5381": [
[0, 0, 0, "React Hook \\"useDashboardEditPageNav\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"],
[0, 0, 0, "React Hook \\"useSaveDashboard\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"],
[0, 0, 0, "React Hook \\"useState\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"],
[0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "3"]
],
"public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.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"],
@@ -1772,11 +1891,19 @@ 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.", "8"],
[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.", "9"]
],
"public/app/features/dashboard-scene/settings/variables/LocalVariableEditableElement.tsx:5381": [
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
],
"public/app/features/dashboard-scene/settings/variables/VariableEditableElement.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.", "0"],
[0, 0, 0, "React Hook \\"useLocalVariableOptions\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"],
[0, 0, 0, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"],
[0, 0, 0, "React Hook \\"useVariableSelectionOptionsCategory\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "3"],
[0, 0, 0, "React Hook \\"useVariableTypeCategory\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "4"]
],
"public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.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, "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, "React Hook \\"useMemo\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
],
"public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.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"]
@@ -2231,6 +2358,10 @@ exports[`better eslint`] = {
[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, "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.", "1"]
],
"public/app/features/datasources/components/EditDataSource.test.tsx:5381": [
[0, 0, 0, "React Hook \\"useEffect\\" is called in function \\"component\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"],
[0, 0, 0, "React Hook \\"useEffect\\" is called in function \\"component\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "1"]
],
"public/app/features/datasources/components/picker/DataSourceCard.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"]
],
@@ -2311,9 +2442,16 @@ 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/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx:5381": [
[0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"],
[0, 0, 0, "React Hook \\"usePluginLinks\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"]
],
"public/app/features/explore/TraceView/components/TracePageHeader/index.tsx:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"]
],
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx:5381": [
[0, 0, 0, "React Hook \\"usePluginLinks\\" is called in function \\"getSpanDetailLinkButtons\\" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word \\"use\\".", "0"]
],
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx:5381": [
[0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "0"]
],
@@ -2424,6 +2562,19 @@ exports[`better eslint`] = {
"public/app/features/live/centrifuge/channel.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/logs/components/panel/LogLineDetailsFields.tsx:5381": [
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "1"],
[0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "2"],
[0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "3"],
[0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "4"],
[0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "5"]
],
"public/app/features/logs/components/panel/LogList.tsx:5381": [
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "1"],
[0, 0, 0, "React Hook \\"useCallback\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "2"]
],
"public/app/features/logs/logsFrame.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
@@ -2615,14 +2766,24 @@ 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/features/provisioning/GettingStarted/Sidebar.tsx:5381": [
[0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"]
],
"public/app/features/provisioning/GettingStarted/SidebarItem.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"]
],
"public/app/features/provisioning/Job/JobContent.tsx:5381": [
[0, 0, 0, "React Hook \\"useEffect\\" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?", "0"]
],
"public/app/features/provisioning/Job/RecentJobs.tsx:5381": [
[0, 0, 0, "React Hook \\"useMemo\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"]
],
"public/app/features/query/components/QueryEditorRow.tsx: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, "Use data-testid for E2E selectors instead of aria-label", "3"]
[0, 0, 0, "React Hook \\"usePluginComponents\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "3"],
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
],
"public/app/features/query/components/QueryEditorRowHeader.tsx:5381": [
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
@@ -2793,6 +2954,9 @@ exports[`better eslint`] = {
"public/app/features/transformers/editors/GroupToNestedTableTransformerEditor.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"]
],
"public/app/features/transformers/editors/OrganizeFieldsTransformerEditor.tsx:5381": [
[0, 0, 0, "React Hook \\"useStyles2\\" is called conditionally. React Hooks must be called in the exact same order in every component render.", "0"]
],
"public/app/features/transformers/editors/ReduceTransformerEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
-13
View File
@@ -1,13 +0,0 @@
# Ignore everything
*
# But not these files:
!.gitignore
!*.mod
!*.sum
!README.md
!Variables.mk
!variables.env
*tmp.mod
-14
View File
@@ -1,14 +0,0 @@
# Project Development Dependencies.
This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo.
- Run `bingo get` to install all tools having each own module file in this directory.
- Run `bingo get <tool>` to install <tool> that have own module file in this directory.
- For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $(<upper case tool name>) variable where <tool> is the .bingo/<tool>.mod.
- For shell: Run `source .bingo/variables.env` to source all environment variable for each tool.
- For go: Import `.bingo/variables.go` to for variable names.
- See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies.
## Requirements
- Go 1.14+
-25
View File
@@ -1,25 +0,0 @@
# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT.
# All tools are designed to be build inside $GOBIN.
BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
GOPATH ?= $(shell go env GOPATH)
GOBIN ?= $(firstword $(subst :, ,${GOPATH}))/bin
GO ?= $(shell which go)
# Below generated variables ensure that every time a tool under each variable is invoked, the correct version
# will be used; reinstalling only if needed.
# For example for drone variable:
#
# In your main Makefile (for non array binaries):
#
#include .bingo/Variables.mk # Assuming -dir was set to .bingo .
#
#command: $(DRONE)
# @echo "Running drone"
# @$(DRONE) <flags/args..>
#
DRONE := $(GOBIN)/drone-v1.5.0
$(DRONE): $(BINGO_DIR)/drone.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
@echo "(re)installing $(GOBIN)/drone-v1.5.0"
@cd $(BINGO_DIR) && GOWORK=off CGO_ENABLED=0 $(GO) build -mod=mod -modfile=drone.mod -o=$(GOBIN)/drone-v1.5.0 "github.com/drone/drone-cli/drone"
-7
View File
@@ -1,7 +0,0 @@
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.17
replace github.com/docker/docker => github.com/docker/engine v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
require github.com/drone/drone-cli v1.5.0 // drone CGO_ENABLED=0
-1010
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files.
-12
View File
@@ -1,12 +0,0 @@
# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT.
# All tools are designed to be build inside $GOBIN.
# Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk.
GOBIN=${GOBIN:=$(go env GOBIN)}
if [ -z "$GOBIN" ]; then
GOBIN="$(go env GOPATH)/bin"
fi
DRONE="${GOBIN}/drone-v1.5.0"
-1
View File
@@ -58,7 +58,6 @@
/go.sum @grafana/grafana-backend-group
/go.work @grafana/grafana-app-platform-squad
/go.work.sum @grafana/grafana-app-platform-squad
/.bingo/ @grafana/grafana-backend-group
/.citools @grafana/grafana-developer-enablement-squad
/pkg/README.md @grafana/grafana-backend-group
/pkg/ruleguard.rules.go @grafana/grafana-backend-group
@@ -98,7 +98,6 @@ runs:
- 'proto/**'
- '**/Makefile'
- 'scripts/**'
- '!scripts/drone/**'
- '!**.md'
- '.github/actions/change-detection/**'
- '**.cue'
@@ -123,7 +122,6 @@ runs:
- 'scripts/**'
- '!**.md'
- '.citools/**'
- '.bingo/**'
- '.github/actions/change-detection/**'
- '${{ inputs.self }}'
docs:
-10
View File
@@ -71,16 +71,6 @@
"action": "updateLabel",
"addLabel": "type/build-packaging"
},
{
"type": "changedfiles",
"matches": [
"scripts/*.star",
".drone.star",
".drone.yml"
],
"action": "updateLabel",
"addLabel": "type/ci"
},
{
"type": "changedfiles",
"matches": [
+1 -1
View File
@@ -13,7 +13,7 @@ permissions: {}
jobs:
backport:
# Only run this job if the triggering workflow was not skipped (and on grafana repo)
if: github.repository == 'grafana/grafana' && github.event.workflow_run.conclusion == 'success'
if: github.event.workflow_run.head_repository.fork == false && github.repository == 'grafana/grafana' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
id-token: write
+25 -2
View File
@@ -23,7 +23,28 @@ permissions:
security-events: write
jobs:
detect-changes:
name: Detect whether code changed
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
javascript: ${{ steps.detect-changes.outputs.frontend }}
go: ${{ steps.detect-changes.outputs.backend }}
actions: 'true'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true # required to get more history in the changed-files action
fetch-depth: 2
- name: Detect changes
id: detect-changes
uses: ./.github/actions/change-detection
with:
self: .github/workflows/codeql-analysis.yml
analyze:
needs: detect-changes
name: Analyze
runs-on: ubuntu-latest
continue-on-error: true # doesn't block PRs from being merged if this fails
@@ -41,6 +62,7 @@ jobs:
steps:
- name: Checkout repository
if: needs.detect-changes.outputs[matrix.language] == 'true'
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
@@ -48,7 +70,7 @@ jobs:
fetch-depth: 2
persist-credentials: false
- if: matrix.language == 'go'
- if: matrix.language == 'go' && needs.detect-changes.outputs.go == 'true'
name: Set go version
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
with:
@@ -56,6 +78,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
if: needs.detect-changes.outputs[matrix.language] == 'true'
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
@@ -64,7 +87,7 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- if: matrix.language == 'go'
- if: matrix.language == 'go' && needs.detect-changes.outputs.go == 'true'
name: Build go files
run: |
go mod verify
+60 -3
View File
@@ -10,7 +10,7 @@ on:
schedule:
# Every weeknight at midnight
# "Scheduled workflows will only run on the default branch." (docs.github.com)
- cron: "0 0 * * 1-5"
- cron: '0 0 * * 1-5'
push:
branches:
- release-*.*.*
@@ -108,7 +108,7 @@ jobs:
github-token: ${{ steps.generate_token.outputs.token }}
script: |
const {REF, VERSION, BUILD_ID, BUCKET, GRAFANA_COMMIT, SOURCE_EVENT} = process.env;
await github.rest.actions.createWorkflowDispatch({
owner: 'grafana',
repo: 'grafana-enterprise',
@@ -166,7 +166,7 @@ jobs:
artifacts: targz:grafana:darwin/arm64
verify: true
steps:
- uses: grafana/shared-workflows/actions/dockerhub-login@main
- uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2
- uses: actions/checkout@v4
with:
persist-credentials: false
@@ -209,3 +209,60 @@ jobs:
run-id: ${{ github.run_id }}
bucket-path: ${{ needs.setup.outputs.version }}_${{ github.run_id }}
environment: prod
publish-dockerhub:
if: github.ref_name == 'main'
permissions:
contents: read
id-token: write
runs-on: ubuntu-x64-small
needs:
- setup
- build
steps:
- uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: artifacts-list-linux-amd64
path: .
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: artifacts-list-linux-arm64
path: .
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: artifacts-list-linux-armv7
path: .
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: artifacts-linux-amd64
path: dist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: artifacts-linux-arm64
path: dist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: artifacts-linux-armv7
path: dist
- name: Push to Docker Hub
env:
VERSION: ${{ needs.setup.outputs.version }}
run: |
# grep can use a wildcard but then it includes the filename as part of the result and that gets complicated.
# It's easier to use cat to combine the artifact lists
cat artifacts-*.txt > artifacts.txt
grep 'grafana_.*docker.tar.gz$' artifacts.txt | xargs -I % docker load -i % | sed 's/Loaded image: //g' | tee docker_images
while read -r line; do
# This tag will be `grafana/grafana-image-tags:...`
docker push "$line"
done < docker_images
docker manifest create grafana/grafana:main "grafana/grafana-image-tags:${VERSION}-amd64" "grafana/grafana-image-tags:${VERSION}-arm64" "grafana/grafana-image-tags:${VERSION}-armv7"
docker manifest create grafana/grafana:main-ubuntu "grafana/grafana-image-tags:${VERSION}-ubuntu-amd64" "grafana/grafana-image-tags:${VERSION}-ubuntu-arm64" "grafana/grafana-image-tags:${VERSION}-ubuntu-armv7"
docker manifest create "grafana/grafana-dev:${VERSION}" "grafana/grafana-image-tags:${VERSION}-amd64" "grafana/grafana-image-tags:${VERSION}-arm64" "grafana/grafana-image-tags:${VERSION}-armv7"
docker manifest create "grafana/grafana-dev:${VERSION}-ubuntu" "grafana/grafana-image-tags:${VERSION}-ubuntu-amd64" "grafana/grafana-image-tags:${VERSION}-ubuntu-arm64" "grafana/grafana-image-tags:${VERSION}-ubuntu-armv7"
docker manifest push grafana/grafana:main
docker manifest push grafana/grafana:main-ubuntu
docker manifest push "grafana/grafana-dev:${VERSION}"
docker manifest push "grafana/grafana-dev:${VERSION}-ubuntu"
-6
View File
@@ -62,7 +62,6 @@ ARG COMMIT_SHA=""
ARG BUILD_BRANCH=""
ARG GO_BUILD_TAGS="oss"
ARG WIRE_TAGS="oss"
ARG BINGO="true"
RUN if grep -i -q alpine /etc/issue; then \
apk add --no-cache \
@@ -76,7 +75,6 @@ RUN if grep -i -q alpine /etc/issue; then \
WORKDIR /tmp/grafana
COPY go.* ./
COPY .bingo .bingo
COPY .citools .citools
# Copy go dependencies first
@@ -109,10 +107,6 @@ COPY pkg/codegen pkg/codegen
COPY pkg/plugins/codegen pkg/plugins/codegen
RUN go mod download
RUN if [[ "$BINGO" = "true" ]]; then \
go install github.com/bwplotka/bingo@latest && \
bingo get -v; \
fi
COPY embed.go Makefile build.go package.json ./
COPY cue.mod cue.mod
+1 -4
View File
@@ -5,7 +5,6 @@
WIRE_TAGS = "oss"
-include local/Makefile
include .bingo/Variables.mk
include .citools/Variables.mk
GO = go
@@ -200,7 +199,7 @@ gen-enterprise-go: ## Generate Wire graph (Enterprise)
$(GO) run ./pkg/build/wire/cmd/wire/main.go gen -tags "enterprise" -gen_tags "(enterprise || pro)" -output_file_prefix="enterprise_" ./pkg/server
endif
gen-go: gen-enterprise-go ## Generate Wire graph
@echo "generatng Wire graph"
@echo "generating Wire graph"
$(GO) run ./pkg/build/wire/cmd/wire/main.go gen -tags "oss" -gen_tags "(!enterprise && !pro)" ./pkg/server
.PHONY: fix-cue
@@ -421,7 +420,6 @@ build-docker-full: ## Build Docker image for development.
tar -ch . | \
docker buildx build - \
--platform $(PLATFORM) \
--build-arg BINGO=false \
--build-arg NODE_ENV=$(DOCKER_JS_NODE_ENV_FLAG) \
--build-arg JS_NODE_ENV=$(DOCKER_JS_NODE_ENV_FLAG) \
--build-arg JS_YARN_INSTALL_FLAG=$(DOCKER_JS_YARN_INSTALL_FLAG) \
@@ -439,7 +437,6 @@ build-docker-full-ubuntu: ## Build Docker image based on Ubuntu for development.
tar -ch . | \
docker buildx build - \
--platform $(PLATFORM) \
--build-arg BINGO=false \
--build-arg NODE_ENV=$(DOCKER_JS_NODE_ENV_FLAG) \
--build-arg JS_NODE_ENV=$(DOCKER_JS_NODE_ENV_FLAG) \
--build-arg JS_YARN_INSTALL_FLAG=$(DOCKER_JS_YARN_INSTALL_FLAG) \
@@ -5,7 +5,7 @@ import (
)
const (
MIN_VERSION = 14
MIN_VERSION = 13
LATEST_VERSION = 41
)
@@ -38,6 +38,7 @@ type PanelPluginInfoProvider interface {
func GetMigrations(dsInfoProvider DataSourceInfoProvider, panelProvider PanelPluginInfoProvider) map[int]SchemaVersionMigrationFunc {
return map[int]SchemaVersionMigrationFunc{
14: V14,
15: V15,
16: V16,
17: V17,
@@ -0,0 +1,38 @@
package schemaversion
// V14 migrates the sharedCrosshair boolean property to graphTooltip integer property.
// This migration converts the old boolean shared crosshair setting to the new integer-based
// graph tooltip setting for consistency with updated dashboard tooltip behavior.
// Example before migration:
// {
// "schemaVersion": 13,
// "title": "My Dashboard",
// "sharedCrosshair": true,
// "panels": [...]
// }
// Example after migration:
// {
// "schemaVersion": 14,
// "title": "My Dashboard",
// "graphTooltip": 1,
// "panels": [...]
// }
func V14(dashboard map[string]interface{}) error {
// Convert sharedCrosshair boolean to graphTooltip integer
sharedCrosshair := GetBoolValue(dashboard, "sharedCrosshair")
if sharedCrosshair {
dashboard["graphTooltip"] = 1
} else {
dashboard["graphTooltip"] = 0
}
// Remove the old sharedCrosshair property
delete(dashboard, "sharedCrosshair")
dashboard["schemaVersion"] = 14
return nil
}
@@ -0,0 +1,93 @@
package schemaversion_test
import (
"testing"
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
)
func TestV14(t *testing.T) {
tests := []migrationTestCase{
{
name: "v14 migration converts sharedCrosshair true to graphTooltip 1",
input: map[string]interface{}{
"title": "V14 Migration Test Dashboard",
"schemaVersion": 13,
"sharedCrosshair": true,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "CPU Usage",
"id": 1,
},
},
},
expected: map[string]interface{}{
"title": "V14 Migration Test Dashboard",
"schemaVersion": 14,
"graphTooltip": 1,
"panels": []interface{}{
map[string]interface{}{
"type": "graph",
"title": "CPU Usage",
"id": 1,
},
},
},
},
{
name: "v14 migration converts sharedCrosshair false to graphTooltip 0",
input: map[string]interface{}{
"title": "V14 Migration Test Dashboard",
"schemaVersion": 13,
"sharedCrosshair": false,
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Memory Usage",
"id": 2,
},
},
},
expected: map[string]interface{}{
"title": "V14 Migration Test Dashboard",
"schemaVersion": 14,
"graphTooltip": 0,
"panels": []interface{}{
map[string]interface{}{
"type": "singlestat",
"title": "Memory Usage",
"id": 2,
},
},
},
},
{
name: "v14 migration handles missing sharedCrosshair (defaults to false/0)",
input: map[string]interface{}{
"title": "V14 Migration Test Dashboard",
"schemaVersion": 13,
"panels": []interface{}{
map[string]interface{}{
"type": "table",
"title": "Server Stats",
"id": 3,
},
},
},
expected: map[string]interface{}{
"title": "V14 Migration Test Dashboard",
"schemaVersion": 14,
"graphTooltip": 0,
"panels": []interface{}{
map[string]interface{}{
"type": "table",
"title": "Server Stats",
"id": 3,
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V14)
}
@@ -0,0 +1,65 @@
{
"title": "V14 Shared Crosshair Migration Test Dashboard",
"schemaVersion": 13,
"sharedCrosshair": true,
"panels": [
{
"type": "graph",
"title": "CPU Usage Over Time",
"id": 1,
"targets": [
{
"expr": "cpu_usage",
"refId": "A"
}
],
"yAxes": [
{
"show": true,
"min": null,
"max": null
}
]
},
{
"type": "graph",
"title": "Memory Usage",
"id": 2,
"targets": [
{
"expr": "memory_usage",
"refId": "B"
}
],
"yAxes": [
{
"show": true,
"min": 0,
"max": 100
}
]
}
],
"templating": {
"list": [
{
"name": "server",
"type": "query",
"datasource": "prometheus",
"query": "label_values(server)",
"refresh": 1,
"options": []
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
},
"annotations": {
"list": []
}
}
@@ -0,0 +1,99 @@
{
"annotations": {
"list": []
},
"graphTooltip": 1,
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 1,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"expr": "cpu_usage",
"refId": "A"
}
],
"title": "CPU Usage Over Time",
"type": "graph",
"yAxes": [
{
"max": null,
"min": null,
"show": true
}
]
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"id": 2,
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"expr": "memory_usage",
"refId": "B"
}
],
"title": "Memory Usage",
"type": "graph",
"yAxes": [
{
"max": 100,
"min": 0,
"show": true
}
]
}
],
"refresh": "",
"schemaVersion": 41,
"templating": {
"list": [
{
"datasource": {
"uid": "prometheus"
},
"name": "server",
"options": [],
"query": "label_values(server)",
"refresh": 1,
"type": "query"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"title": "V14 Shared Crosshair Migration Test Dashboard"
}
+1 -1
View File
@@ -138,7 +138,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/google/wire v0.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1 // indirect
github.com/grafana/alerting v0.0.0-20250821181654-10915888e4f0 // indirect
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect
+2 -2
View File
@@ -673,8 +673,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1 h1:nn19bwMFBGWOvUTxZsdewOfRiXERhKHp0jEira8uQA8=
github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
github.com/grafana/alerting v0.0.0-20250821181654-10915888e4f0 h1:CC+ShIgoLTBsAulRfJH9xF++Us6Y3va3VFbeDeJym2M=
github.com/grafana/alerting v0.0.0-20250821181654-10915888e4f0/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ=
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw=
-18
View File
@@ -3,7 +3,6 @@ module github.com/grafana/grafana/apps/provisioning
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.40.3
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2
k8s.io/apimachinery v0.33.3
k8s.io/client-go v0.33.3
@@ -12,48 +11,31 @@ require (
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-test/deep v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grafana/grafana-app-sdk/logging v0.40.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/onsi/gomega v1.36.2 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
-43
View File
@@ -1,7 +1,3 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -10,8 +6,6 @@ github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtz
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
@@ -22,8 +16,6 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
@@ -36,31 +28,18 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhckclH/N4DYU=
github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk=
github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k=
github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -68,33 +47,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
@@ -105,16 +68,10 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
@@ -1,18 +0,0 @@
package v0alpha1
import "k8s.io/apimachinery/pkg/runtime/schema"
const (
// APIGroup is the API group used by all kinds in this package
APIGroup = "provisioning.grafana.app"
// APIVersion is the API version used by all kinds in this package
APIVersion = "v0alpha1"
)
var (
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
GroupVersion = schema.GroupVersion{
Group: APIGroup,
Version: APIVersion,
}
)
@@ -1,28 +0,0 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"encoding/json"
"io"
"github.com/grafana/grafana-app-sdk/resource"
)
// RepositoryJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
type RepositoryJSONCodec struct{}
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
func (*RepositoryJSONCodec) Read(reader io.Reader, into resource.Object) error {
return json.NewDecoder(reader).Decode(into)
}
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
func (*RepositoryJSONCodec) Write(writer io.Writer, from resource.Object) error {
return json.NewEncoder(writer).Encode(from)
}
// Interface compliance checks
var _ resource.Codec = &RepositoryJSONCodec{}
@@ -1,31 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
import (
time "time"
)
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
type RepositoryMetadata struct {
UpdateTimestamp time.Time `json:"updateTimestamp"`
CreatedBy string `json:"createdBy"`
Uid string `json:"uid"`
CreationTimestamp time.Time `json:"creationTimestamp"`
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
Finalizers []string `json:"finalizers"`
ResourceVersion string `json:"resourceVersion"`
Generation int64 `json:"generation"`
UpdatedBy string `json:"updatedBy"`
Labels map[string]string `json:"labels"`
}
// NewRepositoryMetadata creates a new RepositoryMetadata object.
func NewRepositoryMetadata() *RepositoryMetadata {
return &RepositoryMetadata{
Finalizers: []string{},
Labels: map[string]string{},
}
}
@@ -1,319 +0,0 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"fmt"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"time"
)
// +k8s:openapi-gen=true
type Repository struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
// Spec is the spec of the Repository
Spec RepositorySpec `json:"spec" yaml:"spec"`
Status RepositoryStatus `json:"status" yaml:"status"`
}
func (o *Repository) GetSpec() any {
return o.Spec
}
func (o *Repository) SetSpec(spec any) error {
cast, ok := spec.(RepositorySpec)
if !ok {
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
}
o.Spec = cast
return nil
}
func (o *Repository) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
}
func (o *Repository) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
}
func (o *Repository) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(RepositoryStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type RepositoryStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
}
func (o *Repository) GetStaticMetadata() resource.StaticMetadata {
gvk := o.GroupVersionKind()
return resource.StaticMetadata{
Name: o.ObjectMeta.Name,
Namespace: o.ObjectMeta.Namespace,
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
}
func (o *Repository) SetStaticMetadata(metadata resource.StaticMetadata) {
o.Name = metadata.Name
o.Namespace = metadata.Namespace
o.SetGroupVersionKind(schema.GroupVersionKind{
Group: metadata.Group,
Version: metadata.Version,
Kind: metadata.Kind,
})
}
func (o *Repository) GetCommonMetadata() resource.CommonMetadata {
dt := o.DeletionTimestamp
var deletionTimestamp *time.Time
if dt != nil {
deletionTimestamp = &dt.Time
}
// Legacy ExtraFields support
extraFields := make(map[string]any)
if o.Annotations != nil {
extraFields["annotations"] = o.Annotations
}
if o.ManagedFields != nil {
extraFields["managedFields"] = o.ManagedFields
}
if o.OwnerReferences != nil {
extraFields["ownerReferences"] = o.OwnerReferences
}
return resource.CommonMetadata{
UID: string(o.UID),
ResourceVersion: o.ResourceVersion,
Generation: o.Generation,
Labels: o.Labels,
CreationTimestamp: o.CreationTimestamp.Time,
DeletionTimestamp: deletionTimestamp,
Finalizers: o.Finalizers,
UpdateTimestamp: o.GetUpdateTimestamp(),
CreatedBy: o.GetCreatedBy(),
UpdatedBy: o.GetUpdatedBy(),
ExtraFields: extraFields,
}
}
func (o *Repository) SetCommonMetadata(metadata resource.CommonMetadata) {
o.UID = types.UID(metadata.UID)
o.ResourceVersion = metadata.ResourceVersion
o.Generation = metadata.Generation
o.Labels = metadata.Labels
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
if metadata.DeletionTimestamp != nil {
dt := metav1.NewTime(*metadata.DeletionTimestamp)
o.DeletionTimestamp = &dt
} else {
o.DeletionTimestamp = nil
}
o.Finalizers = metadata.Finalizers
if o.Annotations == nil {
o.Annotations = make(map[string]string)
}
if !metadata.UpdateTimestamp.IsZero() {
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
}
if metadata.CreatedBy != "" {
o.SetCreatedBy(metadata.CreatedBy)
}
if metadata.UpdatedBy != "" {
o.SetUpdatedBy(metadata.UpdatedBy)
}
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
if metadata.ExtraFields != nil {
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
if cast, ok := annotations.(map[string]string); ok {
o.Annotations = cast
}
}
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
o.ManagedFields = cast
}
}
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
o.OwnerReferences = cast
}
}
}
}
func (o *Repository) GetCreatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
}
func (o *Repository) SetCreatedBy(createdBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
}
func (o *Repository) GetUpdateTimestamp() time.Time {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
return parsed
}
func (o *Repository) SetUpdateTimestamp(updateTimestamp time.Time) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
}
func (o *Repository) GetUpdatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
}
func (o *Repository) SetUpdatedBy(updatedBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
}
func (o *Repository) Copy() resource.Object {
return resource.CopyObject(o)
}
func (o *Repository) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *Repository) DeepCopy() *Repository {
cpy := &Repository{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *Repository) DeepCopyInto(dst *Repository) {
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
dst.TypeMeta.Kind = o.TypeMeta.Kind
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
o.Spec.DeepCopyInto(&dst.Spec)
o.Status.DeepCopyInto(&dst.Status)
}
// Interface compliance compile-time check
var _ resource.Object = &Repository{}
// +k8s:openapi-gen=true
type RepositoryList struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ListMeta `json:"metadata" yaml:"metadata"`
Items []Repository `json:"items" yaml:"items"`
}
func (o *RepositoryList) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *RepositoryList) Copy() resource.ListObject {
cpy := &RepositoryList{
TypeMeta: o.TypeMeta,
Items: make([]Repository, len(o.Items)),
}
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
for i := 0; i < len(o.Items); i++ {
if item, ok := o.Items[i].Copy().(*Repository); ok {
cpy.Items[i] = *item
}
}
return cpy
}
func (o *RepositoryList) GetItems() []resource.Object {
items := make([]resource.Object, len(o.Items))
for i := 0; i < len(o.Items); i++ {
items[i] = &o.Items[i]
}
return items
}
func (o *RepositoryList) SetItems(items []resource.Object) {
o.Items = make([]Repository, len(items))
for i := 0; i < len(items); i++ {
o.Items[i] = *items[i].(*Repository)
}
}
func (o *RepositoryList) DeepCopy() *RepositoryList {
cpy := &RepositoryList{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *RepositoryList) DeepCopyInto(dst *RepositoryList) {
resource.CopyObjectInto(dst, o)
}
// Interface compliance compile-time check
var _ resource.ListObject = &RepositoryList{}
// Copy methods for all subresource types
// DeepCopy creates a full deep copy of Spec
func (s *RepositorySpec) DeepCopy() *RepositorySpec {
cpy := &RepositorySpec{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies Spec into another Spec object
func (s *RepositorySpec) DeepCopyInto(dst *RepositorySpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of RepositoryStatus
func (s *RepositoryStatus) DeepCopy() *RepositoryStatus {
cpy := &RepositoryStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies RepositoryStatus into another RepositoryStatus object
func (s *RepositoryStatus) DeepCopyInto(dst *RepositoryStatus) {
resource.CopyObjectInto(dst, s)
}
@@ -1,34 +0,0 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"github.com/grafana/grafana-app-sdk/resource"
)
// schema is unexported to prevent accidental overwrites
var (
schemaRepository = resource.NewSimpleSchema("provisioning.grafana.app", "v0alpha1", &Repository{}, &RepositoryList{}, resource.WithKind("Repository"),
resource.WithPlural("repositories"), resource.WithScope(resource.NamespacedScope))
kindRepository = resource.Kind{
Schema: schemaRepository,
Codecs: map[resource.KindEncoding]resource.Codec{
resource.KindEncodingJSON: &RepositoryJSONCodec{},
},
}
)
// Kind returns a resource.Kind for this Schema with a JSON codec
func RepositoryKind() resource.Kind {
return kindRepository
}
// Schema returns a resource.SimpleSchema representation of Repository
func RepositorySchema() *resource.SimpleSchema {
return schemaRepository
}
// Interface compliance checks
var _ resource.Schema = kindRepository
@@ -1,169 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// +k8s:openapi-gen=true
type RepositorySyncOptions struct {
// Enabled must be saved as true before any sync job will run
Enabled bool `json:"enabled"`
// Where values should be saved
Target RepositorySyncOptionsTarget `json:"target"`
// When non-zero, the sync will run periodically
IntervalSeconds *int64 `json:"intervalSeconds,omitempty"`
}
// NewRepositorySyncOptions creates a new RepositorySyncOptions object.
func NewRepositorySyncOptions() *RepositorySyncOptions {
return &RepositorySyncOptions{}
}
// +k8s:openapi-gen=true
type RepositoryLocalRepositoryConfig struct {
// Path to the local repository
Path string `json:"path"`
}
// NewRepositoryLocalRepositoryConfig creates a new RepositoryLocalRepositoryConfig object.
func NewRepositoryLocalRepositoryConfig() *RepositoryLocalRepositoryConfig {
return &RepositoryLocalRepositoryConfig{}
}
// +k8s:openapi-gen=true
type RepositoryGitHubRepositoryConfig struct {
// The repository URL (e.g. `https://github.com/example/test`).
Url *string `json:"url,omitempty"`
// The branch to use in the repository.
Branch string `json:"branch"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token *string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
EncryptedToken []string `json:"encryptedToken,omitempty"`
// Whether we should show dashboard previews for pull requests.
// By default, this is false (i.e. we will not create previews).
GenerateDashboardPreviews *bool `json:"generateDashboardPreviews,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
Path *string `json:"path,omitempty"`
}
// NewRepositoryGitHubRepositoryConfig creates a new RepositoryGitHubRepositoryConfig object.
func NewRepositoryGitHubRepositoryConfig() *RepositoryGitHubRepositoryConfig {
return &RepositoryGitHubRepositoryConfig{}
}
// +k8s:openapi-gen=true
type RepositoryGitRepositoryConfig struct {
// The repository URL (e.g. `https://github.com/example/test.git`).
Url *string `json:"url,omitempty"`
// The branch to use in the repository.
Branch string `json:"branch"`
// TokenUser is the user that will be used to access the repository if it's a personal access token.
TokenUser *string `json:"tokenUser,omitempty"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token *string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
EncryptedToken []string `json:"encryptedToken,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
Path *string `json:"path,omitempty"`
}
// NewRepositoryGitRepositoryConfig creates a new RepositoryGitRepositoryConfig object.
func NewRepositoryGitRepositoryConfig() *RepositoryGitRepositoryConfig {
return &RepositoryGitRepositoryConfig{}
}
// +k8s:openapi-gen=true
type RepositoryBitbucketRepositoryConfig struct {
// The repository URL (e.g. `https://bitbucket.org/example/test`).
Url *string `json:"url,omitempty"`
// The branch to use in the repository.
Branch string `json:"branch"`
// TokenUser is the user that will be used to access the repository if it's a personal access token.
TokenUser *string `json:"tokenUser,omitempty"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token *string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
EncryptedToken []string `json:"encryptedToken,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
Path *string `json:"path,omitempty"`
}
// NewRepositoryBitbucketRepositoryConfig creates a new RepositoryBitbucketRepositoryConfig object.
func NewRepositoryBitbucketRepositoryConfig() *RepositoryBitbucketRepositoryConfig {
return &RepositoryBitbucketRepositoryConfig{}
}
// +k8s:openapi-gen=true
type RepositoryGitLabRepositoryConfig struct {
// The repository URL (e.g. `https://gitlab.com/example/test`).
Url *string `json:"url,omitempty"`
// The branch to use in the repository.
Branch string `json:"branch"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token *string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
EncryptedToken []string `json:"encryptedToken,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
Path *string `json:"path,omitempty"`
}
// NewRepositoryGitLabRepositoryConfig creates a new RepositoryGitLabRepositoryConfig object.
func NewRepositoryGitLabRepositoryConfig() *RepositoryGitLabRepositoryConfig {
return &RepositoryGitLabRepositoryConfig{}
}
// +k8s:openapi-gen=true
type RepositorySpec struct {
// The repository display name (shown in the UI)
Title string `json:"title"`
// Repository description
Description *string `json:"description,omitempty"`
// UI driven Workflow that allow changes to the contends of the repository.
// The order is relevant for defining the precedence of the workflows.
// When empty, the repository does not support any edits (eg, readonly)
Workflows []string `json:"workflows,omitempty"`
// Sync settings -- how values are pulled from the repository into grafana
Sync RepositorySyncOptions `json:"sync"`
// The repository type. When selected oneOf the values below should be non-nil
Type RepositorySpecType `json:"type"`
// The repository on the local file system.
// Mutually exclusive with local | github.
Local *RepositoryLocalRepositoryConfig `json:"local,omitempty"`
// The repository on GitHub.
// Mutually exclusive with local | github | git.
Github *RepositoryGitHubRepositoryConfig `json:"github,omitempty"`
// The repository on Git.
// Mutually exclusive with local | github | git.
Git *RepositoryGitRepositoryConfig `json:"git,omitempty"`
// The repository on Bitbucket.
// Mutually exclusive with local | github | git.
Bitbucket *RepositoryBitbucketRepositoryConfig `json:"bitbucket,omitempty"`
// The repository on GitLab.
// Mutually exclusive with local | github | git.
Gitlab *RepositoryGitLabRepositoryConfig `json:"gitlab,omitempty"`
}
// NewRepositorySpec creates a new RepositorySpec object.
func NewRepositorySpec() *RepositorySpec {
return &RepositorySpec{
Sync: *NewRepositorySyncOptions(),
}
}
// +k8s:openapi-gen=true
type RepositorySyncOptionsTarget string
const (
RepositorySyncOptionsTargetUnified RepositorySyncOptionsTarget = "unified"
RepositorySyncOptionsTargetLegacy RepositorySyncOptionsTarget = "legacy"
)
// +k8s:openapi-gen=true
type RepositorySpecType string
const (
RepositorySpecTypeLocal RepositorySpecType = "local"
RepositorySpecTypeGithub RepositorySpecType = "github"
RepositorySpecTypeGit RepositorySpecType = "git"
RepositorySpecTypeBitbucket RepositorySpecType = "bitbucket"
RepositorySpecTypeGitlab RepositorySpecType = "gitlab"
)
@@ -1,137 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// +k8s:openapi-gen=true
type RepositoryHealthStatus struct {
// When not healthy, requests will not be executed
Healthy bool `json:"healthy"`
// When the health was checked last time
Checked *int64 `json:"checked,omitempty"`
// Summary messages (can be shown to users)
// Will only be populated when not healthy
Message []string `json:"message,omitempty"`
}
// NewRepositoryHealthStatus creates a new RepositoryHealthStatus object.
func NewRepositoryHealthStatus() *RepositoryHealthStatus {
return &RepositoryHealthStatus{}
}
// +k8s:openapi-gen=true
type RepositorySyncStatus struct {
// pending, running, success, error
State RepositorySyncStatusState `json:"state"`
// The ID for the job that ran this sync
Job *string `json:"job,omitempty"`
// When the sync job started
Started *int64 `json:"started,omitempty"`
// When the sync job finished
Finished *int64 `json:"finished,omitempty"`
// When the next sync check is scheduled
Scheduled *int64 `json:"scheduled,omitempty"`
// Summary messages (will be shown to users)
Message []string `json:"message"`
// The repository ref when the last successful sync ran
LastRef *string `json:"lastRef,omitempty"`
// Incremental synchronization for versioned repositories
Incremental *bool `json:"incremental,omitempty"`
}
// NewRepositorySyncStatus creates a new RepositorySyncStatus object.
func NewRepositorySyncStatus() *RepositorySyncStatus {
return &RepositorySyncStatus{
Message: []string{},
}
}
// +k8s:openapi-gen=true
type RepositoryResourceCount struct {
Group string `json:"group"`
Resource string `json:"resource"`
Count int64 `json:"count"`
}
// NewRepositoryResourceCount creates a new RepositoryResourceCount object.
func NewRepositoryResourceCount() *RepositoryResourceCount {
return &RepositoryResourceCount{}
}
// +k8s:openapi-gen=true
type RepositorystatusOperatorState struct {
// lastEvaluation is the ResourceVersion last evaluated
LastEvaluation string `json:"lastEvaluation"`
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
State RepositoryStatusOperatorStateState `json:"state"`
// descriptiveState is an optional more descriptive state field which has no requirements on format
DescriptiveState *string `json:"descriptiveState,omitempty"`
// details contains any extra information that is operator-specific
Details map[string]interface{} `json:"details,omitempty"`
}
// NewRepositorystatusOperatorState creates a new RepositorystatusOperatorState object.
func NewRepositorystatusOperatorState() *RepositorystatusOperatorState {
return &RepositorystatusOperatorState{}
}
// +k8s:openapi-gen=true
type RepositoryWebhookStatus struct {
Id *int64 `json:"id,omitempty"`
Url *string `json:"url,omitempty"`
Secret *string `json:"secret,omitempty"`
EncryptedSecret []string `json:"encryptedSecret,omitempty"`
SubscribedEvents []string `json:"subscribedEvents,omitempty"`
LastEvent *int64 `json:"lastEvent,omitempty"`
}
// NewRepositoryWebhookStatus creates a new RepositoryWebhookStatus object.
func NewRepositoryWebhookStatus() *RepositoryWebhookStatus {
return &RepositoryWebhookStatus{}
}
// +k8s:openapi-gen=true
type RepositoryStatus struct {
// The generation of the spec last time reconciliation ran
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
// This will get updated with the current health status (and updated periodically)
Health RepositoryHealthStatus `json:"health"`
// Sync information with the last sync information
Sync RepositorySyncStatus `json:"sync"`
// The object count when sync last ran
Stats []RepositoryResourceCount `json:"stats,omitempty"`
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
OperatorStates map[string]RepositorystatusOperatorState `json:"operatorStates,omitempty"`
// Webhook Information (if applicable)
Webhook *RepositoryWebhookStatus `json:"webhook,omitempty"`
// additionalFields is reserved for future use
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
}
// NewRepositoryStatus creates a new RepositoryStatus object.
func NewRepositoryStatus() *RepositoryStatus {
return &RepositoryStatus{
Health: *NewRepositoryHealthStatus(),
Sync: *NewRepositorySyncStatus(),
}
}
// +k8s:openapi-gen=true
type RepositorySyncStatusState string
const (
RepositorySyncStatusStatePending RepositorySyncStatusState = "pending"
RepositorySyncStatusStateRunning RepositorySyncStatusState = "running"
RepositorySyncStatusStateSuccess RepositorySyncStatusState = "success"
RepositorySyncStatusStateError RepositorySyncStatusState = "error"
)
// +k8s:openapi-gen=true
type RepositoryStatusOperatorStateState string
const (
RepositoryStatusOperatorStateStateSuccess RepositoryStatusOperatorStateState = "success"
RepositoryStatusOperatorStateStateInProgress RepositoryStatusOperatorStateState = "in_progress"
RepositoryStatusOperatorStateStateFailed RepositoryStatusOperatorStateState = "failed"
)
@@ -1,879 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by grafana-app-sdk. DO NOT EDIT.
package v0alpha1
import (
common "k8s.io/kube-openapi/pkg/common"
spec "k8s.io/kube-openapi/pkg/validation/spec"
)
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.Repository": schema_provisioning_kinds_provisioning_v0alpha1_Repository(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryBitbucketRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryBitbucketRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitHubRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitHubRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitLabRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitLabRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryHealthStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryHealthStatus(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryList": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryList(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryLocalRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryLocalRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryResourceCount": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryResourceCount(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySpec": schema_provisioning_kinds_provisioning_v0alpha1_RepositorySpec(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryStatus(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncOptions": schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncOptions(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncStatus(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryWebhookStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryWebhookStatus(ref),
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorystatusOperatorState": schema_provisioning_kinds_provisioning_v0alpha1_RepositorystatusOperatorState(ref),
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_Repository(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Description: "Spec is the spec of the Repository",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySpec", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryBitbucketRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"url": {
SchemaProps: spec.SchemaProps{
Description: "The repository URL (e.g. `https://bitbucket.org/example/test`).",
Type: []string{"string"},
Format: "",
},
},
"branch": {
SchemaProps: spec.SchemaProps{
Description: "The branch to use in the repository.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"tokenUser": {
SchemaProps: spec.SchemaProps{
Description: "TokenUser is the user that will be used to access the repository if it's a personal access token.",
Type: []string{"string"},
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"branch"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitHubRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"url": {
SchemaProps: spec.SchemaProps{
Description: "The repository URL (e.g. `https://github.com/example/test`).",
Type: []string{"string"},
Format: "",
},
},
"branch": {
SchemaProps: spec.SchemaProps{
Description: "The branch to use in the repository.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"generateDashboardPreviews": {
SchemaProps: spec.SchemaProps{
Description: "Whether we should show dashboard previews for pull requests. By default, this is false (i.e. we will not create previews).",
Type: []string{"boolean"},
Format: "",
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"branch"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitLabRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"url": {
SchemaProps: spec.SchemaProps{
Description: "The repository URL (e.g. `https://gitlab.com/example/test`).",
Type: []string{"string"},
Format: "",
},
},
"branch": {
SchemaProps: spec.SchemaProps{
Description: "The branch to use in the repository.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"branch"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"url": {
SchemaProps: spec.SchemaProps{
Description: "The repository URL (e.g. `https://github.com/example/test.git`).",
Type: []string{"string"},
Format: "",
},
},
"branch": {
SchemaProps: spec.SchemaProps{
Description: "The branch to use in the repository.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"tokenUser": {
SchemaProps: spec.SchemaProps{
Description: "TokenUser is the user that will be used to access the repository if it's a personal access token.",
Type: []string{"string"},
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"branch"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryHealthStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"healthy": {
SchemaProps: spec.SchemaProps{
Description: "When not healthy, requests will not be executed",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"checked": {
SchemaProps: spec.SchemaProps{
Description: "When the health was checked last time",
Type: []string{"integer"},
Format: "int64",
},
},
"message": {
SchemaProps: spec.SchemaProps{
Description: "Summary messages (can be shown to users) Will only be populated when not healthy",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"healthy"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.Repository"),
},
},
},
},
},
},
Required: []string{"metadata", "items"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.Repository", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryLocalRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path to the local repository",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"path"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryResourceCount(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"group": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"resource": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"count": {
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"integer"},
Format: "int64",
},
},
},
Required: []string{"group", "resource", "count"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"title": {
SchemaProps: spec.SchemaProps{
Description: "The repository display name (shown in the UI)",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"description": {
SchemaProps: spec.SchemaProps{
Description: "Repository description",
Type: []string{"string"},
Format: "",
},
},
"workflows": {
SchemaProps: spec.SchemaProps{
Description: "UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly)",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"sync": {
SchemaProps: spec.SchemaProps{
Description: "Sync settings -- how values are pulled from the repository into grafana",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncOptions"),
},
},
"type": {
SchemaProps: spec.SchemaProps{
Description: "The repository type. When selected oneOf the values below should be non-nil",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"local": {
SchemaProps: spec.SchemaProps{
Description: "The repository on the local file system. Mutually exclusive with local | github.",
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryLocalRepositoryConfig"),
},
},
"github": {
SchemaProps: spec.SchemaProps{
Description: "The repository on GitHub. Mutually exclusive with local | github | git.",
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitHubRepositoryConfig"),
},
},
"git": {
SchemaProps: spec.SchemaProps{
Description: "The repository on Git. Mutually exclusive with local | github | git.",
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitRepositoryConfig"),
},
},
"bitbucket": {
SchemaProps: spec.SchemaProps{
Description: "The repository on Bitbucket. Mutually exclusive with local | github | git.",
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryBitbucketRepositoryConfig"),
},
},
"gitlab": {
SchemaProps: spec.SchemaProps{
Description: "The repository on GitLab. Mutually exclusive with local | github | git.",
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitLabRepositoryConfig"),
},
},
},
Required: []string{"title", "sync", "type"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryBitbucketRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitHubRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitLabRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryLocalRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncOptions"},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"observedGeneration": {
SchemaProps: spec.SchemaProps{
Description: "The generation of the spec last time reconciliation ran",
Type: []string{"integer"},
Format: "int64",
},
},
"health": {
SchemaProps: spec.SchemaProps{
Description: "This will get updated with the current health status (and updated periodically)",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryHealthStatus"),
},
},
"sync": {
SchemaProps: spec.SchemaProps{
Description: "Sync information with the last sync information",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncStatus"),
},
},
"stats": {
SchemaProps: spec.SchemaProps{
Description: "The object count when sync last ran",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryResourceCount"),
},
},
},
},
},
"operatorStates": {
SchemaProps: spec.SchemaProps{
Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorystatusOperatorState"),
},
},
},
},
},
"webhook": {
SchemaProps: spec.SchemaProps{
Description: "Webhook Information (if applicable)",
Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryWebhookStatus"),
},
},
"additionalFields": {
SchemaProps: spec.SchemaProps{
Description: "additionalFields is reserved for future use",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Format: "",
},
},
},
},
},
},
Required: []string{"health", "sync"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryHealthStatus", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryResourceCount", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncStatus", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryWebhookStatus", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorystatusOperatorState"},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"enabled": {
SchemaProps: spec.SchemaProps{
Description: "Enabled must be saved as true before any sync job will run",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"target": {
SchemaProps: spec.SchemaProps{
Description: "Where values should be saved",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"intervalSeconds": {
SchemaProps: spec.SchemaProps{
Description: "When non-zero, the sync will run periodically",
Type: []string{"integer"},
Format: "int64",
},
},
},
Required: []string{"enabled", "target"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"state": {
SchemaProps: spec.SchemaProps{
Description: "pending, running, success, error",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"job": {
SchemaProps: spec.SchemaProps{
Description: "The ID for the job that ran this sync",
Type: []string{"string"},
Format: "",
},
},
"started": {
SchemaProps: spec.SchemaProps{
Description: "When the sync job started",
Type: []string{"integer"},
Format: "int64",
},
},
"finished": {
SchemaProps: spec.SchemaProps{
Description: "When the sync job finished",
Type: []string{"integer"},
Format: "int64",
},
},
"scheduled": {
SchemaProps: spec.SchemaProps{
Description: "When the next sync check is scheduled",
Type: []string{"integer"},
Format: "int64",
},
},
"message": {
SchemaProps: spec.SchemaProps{
Description: "Summary messages (will be shown to users)",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"lastRef": {
SchemaProps: spec.SchemaProps{
Description: "The repository ref when the last successful sync ran",
Type: []string{"string"},
Format: "",
},
},
"incremental": {
SchemaProps: spec.SchemaProps{
Description: "Incremental synchronization for versioned repositories",
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"state", "message"},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryWebhookStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"id": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int64",
},
},
"url": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"secret": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"encryptedSecret": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"subscribedEvents": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"lastEvent": {
SchemaProps: spec.SchemaProps{
Type: []string{"integer"},
Format: "int64",
},
},
},
},
},
}
}
func schema_provisioning_kinds_provisioning_v0alpha1_RepositorystatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"lastEvaluation": {
SchemaProps: spec.SchemaProps{
Description: "lastEvaluation is the ResourceVersion last evaluated",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"state": {
SchemaProps: spec.SchemaProps{
Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"descriptiveState": {
SchemaProps: spec.SchemaProps{
Description: "descriptiveState is an optional more descriptive state field which has no requirements on format",
Type: []string{"string"},
Format: "",
},
},
"details": {
SchemaProps: spec.SchemaProps{
Description: "details contains any extra information that is operator-specific",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Format: "",
},
},
},
},
},
},
Required: []string{"lastEvaluation", "state"},
},
},
}
}
File diff suppressed because one or more lines are too long
@@ -21,12 +21,11 @@ type Repository struct {
Status RepositoryStatus `json:"status,omitempty"`
}
// NOT YET USED FOR REAL -- testing secure value workflow
type SecureValues struct {
// Token used to connect the configured repository
Token common.InlineSecureValue `json:"token,omitzero,omitempty"`
// Some webhooks (github) require a secret key value
// Some webhooks (including github) require a secret key value
WebhookSecret common.InlineSecureValue `json:"webhookSecret,omitzero,omitempty"`
}
@@ -55,11 +54,6 @@ type GitHubRepositoryConfig struct {
// The branch to use in the repository.
Branch string `json:"branch"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
// +listType=atomic
EncryptedToken []byte `json:"encryptedToken,omitempty"`
// Whether we should show dashboard previews for pull requests.
// By default, this is false (i.e. we will not create previews).
@@ -80,11 +74,6 @@ type GitRepositoryConfig struct {
Branch string `json:"branch"`
// TokenUser is the user that will be used to access the repository if it's a personal access token.
TokenUser string `json:"tokenUser,omitempty"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
// +listType=atomic
EncryptedToken []byte `json:"encryptedToken,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
// This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed.
// The path is relative to the root of the repository, regardless of the leading slash.
@@ -100,11 +89,6 @@ type BitbucketRepositoryConfig struct {
Branch string `json:"branch"`
// TokenUser is the user that will be used to access the repository if it's a personal access token.
TokenUser string `json:"tokenUser,omitempty"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
// +listType=atomic
EncryptedToken []byte `json:"encryptedToken,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
// This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed.
// The path is relative to the root of the repository, regardless of the leading slash.
@@ -118,11 +102,6 @@ type GitLabRepositoryConfig struct {
URL string `json:"url,omitempty"`
// The branch to use in the repository.
Branch string `json:"branch"`
// Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
Token string `json:"token,omitempty"`
// Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
// +listType=atomic
EncryptedToken []byte `json:"encryptedToken,omitempty"`
// Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
// This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed.
// The path is relative to the root of the repository, regardless of the leading slash.
@@ -297,8 +276,6 @@ type SyncStatus struct {
type WebhookStatus struct {
ID int64 `json:"id,omitempty"`
URL string `json:"url,omitempty"`
Secret string `json:"secret,omitempty"`
EncryptedSecret []byte `json:"encryptedSecret,omitempty"`
SubscribedEvents []string `json:"subscribedEvents,omitempty"`
LastEvent int64 `json:"lastEvent,omitempty"`
}
@@ -30,11 +30,6 @@ func (in *Author) DeepCopy() *Author {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BitbucketRepositoryConfig) DeepCopyInto(out *BitbucketRepositoryConfig) {
*out = *in
if in.EncryptedToken != nil {
in, out := &in.EncryptedToken, &out.EncryptedToken
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
@@ -156,11 +151,6 @@ func (in *FileList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitHubRepositoryConfig) DeepCopyInto(out *GitHubRepositoryConfig) {
*out = *in
if in.EncryptedToken != nil {
in, out := &in.EncryptedToken, &out.EncryptedToken
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
@@ -177,11 +167,6 @@ func (in *GitHubRepositoryConfig) DeepCopy() *GitHubRepositoryConfig {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitLabRepositoryConfig) DeepCopyInto(out *GitLabRepositoryConfig) {
*out = *in
if in.EncryptedToken != nil {
in, out := &in.EncryptedToken, &out.EncryptedToken
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
@@ -198,11 +183,6 @@ func (in *GitLabRepositoryConfig) DeepCopy() *GitLabRepositoryConfig {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitRepositoryConfig) DeepCopyInto(out *GitRepositoryConfig) {
*out = *in
if in.EncryptedToken != nil {
in, out := &in.EncryptedToken, &out.EncryptedToken
*out = make([]byte, len(*in))
copy(*out, *in)
}
return
}
@@ -738,22 +718,22 @@ func (in *RepositorySpec) DeepCopyInto(out *RepositorySpec) {
if in.GitHub != nil {
in, out := &in.GitHub, &out.GitHub
*out = new(GitHubRepositoryConfig)
(*in).DeepCopyInto(*out)
**out = **in
}
if in.Git != nil {
in, out := &in.Git, &out.Git
*out = new(GitRepositoryConfig)
(*in).DeepCopyInto(*out)
**out = **in
}
if in.Bitbucket != nil {
in, out := &in.Bitbucket, &out.Bitbucket
*out = new(BitbucketRepositoryConfig)
(*in).DeepCopyInto(*out)
**out = **in
}
if in.GitLab != nil {
in, out := &in.GitLab, &out.GitLab
*out = new(GitLabRepositoryConfig)
(*in).DeepCopyInto(*out)
**out = **in
}
return
}
@@ -1220,11 +1200,6 @@ func (in *WebhookResponse) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WebhookStatus) DeepCopyInto(out *WebhookStatus) {
*out = *in
if in.EncryptedSecret != nil {
in, out := &in.EncryptedSecret, &out.EncryptedSecret
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.SubscribedEvents != nil {
in, out := &in.SubscribedEvents, &out.SubscribedEvents
*out = make([]string, len(*in))
@@ -128,25 +128,6 @@ func schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref common.
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"string"},
Format: "byte",
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
@@ -402,25 +383,6 @@ func schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref common.Ref
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"string"},
Format: "byte",
},
},
"generateDashboardPreviews": {
SchemaProps: spec.SchemaProps{
Description: "Whether we should show dashboard previews for pull requests. By default, this is false (i.e. we will not create previews).",
@@ -463,25 +425,6 @@ func schema_pkg_apis_provisioning_v0alpha1_GitLabRepositoryConfig(ref common.Ref
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"string"},
Format: "byte",
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
@@ -524,25 +467,6 @@ func schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref common.Refere
Format: "",
},
},
"token": {
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
Type: []string{"string"},
Format: "",
},
},
"encryptedToken": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
Type: []string{"string"},
Format: "byte",
},
},
"path": {
SchemaProps: spec.SchemaProps{
Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
@@ -2378,8 +2302,7 @@ func schema_pkg_apis_provisioning_v0alpha1_SecureValues(ref common.ReferenceCall
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "NOT YET USED FOR REAL -- testing secure value workflow",
Type: []string{"object"},
Type: []string{"object"},
Properties: map[string]spec.Schema{
"token": {
SchemaProps: spec.SchemaProps{
@@ -2390,7 +2313,7 @@ func schema_pkg_apis_provisioning_v0alpha1_SecureValues(ref common.ReferenceCall
},
"webhookSecret": {
SchemaProps: spec.SchemaProps{
Description: "Some webhooks (github) require a secret key value",
Description: "Some webhooks (including github) require a secret key value",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
},
@@ -2671,18 +2594,6 @@ func schema_pkg_apis_provisioning_v0alpha1_WebhookStatus(ref common.ReferenceCal
Format: "",
},
},
"secret": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"encryptedSecret": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "byte",
},
},
"subscribedEvents": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
@@ -7,12 +7,10 @@ package v0alpha1
// BitbucketRepositoryConfigApplyConfiguration represents a declarative configuration of the BitbucketRepositoryConfig type for use
// with apply.
type BitbucketRepositoryConfigApplyConfiguration struct {
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
TokenUser *string `json:"tokenUser,omitempty"`
Token *string `json:"token,omitempty"`
EncryptedToken []byte `json:"encryptedToken,omitempty"`
Path *string `json:"path,omitempty"`
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
TokenUser *string `json:"tokenUser,omitempty"`
Path *string `json:"path,omitempty"`
}
// BitbucketRepositoryConfigApplyConfiguration constructs a declarative configuration of the BitbucketRepositoryConfig type for use with
@@ -45,24 +43,6 @@ func (b *BitbucketRepositoryConfigApplyConfiguration) WithTokenUser(value string
return b
}
// WithToken sets the Token field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Token field is set to the value of the last call.
func (b *BitbucketRepositoryConfigApplyConfiguration) WithToken(value string) *BitbucketRepositoryConfigApplyConfiguration {
b.Token = &value
return b
}
// WithEncryptedToken adds the given value to the EncryptedToken field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the EncryptedToken field.
func (b *BitbucketRepositoryConfigApplyConfiguration) WithEncryptedToken(values ...byte) *BitbucketRepositoryConfigApplyConfiguration {
for i := range values {
b.EncryptedToken = append(b.EncryptedToken, values[i])
}
return b
}
// WithPath sets the Path field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Path field is set to the value of the last call.
@@ -9,8 +9,6 @@ package v0alpha1
type GitHubRepositoryConfigApplyConfiguration struct {
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
Token *string `json:"token,omitempty"`
EncryptedToken []byte `json:"encryptedToken,omitempty"`
GenerateDashboardPreviews *bool `json:"generateDashboardPreviews,omitempty"`
Path *string `json:"path,omitempty"`
}
@@ -37,24 +35,6 @@ func (b *GitHubRepositoryConfigApplyConfiguration) WithBranch(value string) *Git
return b
}
// WithToken sets the Token field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Token field is set to the value of the last call.
func (b *GitHubRepositoryConfigApplyConfiguration) WithToken(value string) *GitHubRepositoryConfigApplyConfiguration {
b.Token = &value
return b
}
// WithEncryptedToken adds the given value to the EncryptedToken field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the EncryptedToken field.
func (b *GitHubRepositoryConfigApplyConfiguration) WithEncryptedToken(values ...byte) *GitHubRepositoryConfigApplyConfiguration {
for i := range values {
b.EncryptedToken = append(b.EncryptedToken, values[i])
}
return b
}
// WithGenerateDashboardPreviews sets the GenerateDashboardPreviews field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the GenerateDashboardPreviews field is set to the value of the last call.
@@ -7,11 +7,9 @@ package v0alpha1
// GitLabRepositoryConfigApplyConfiguration represents a declarative configuration of the GitLabRepositoryConfig type for use
// with apply.
type GitLabRepositoryConfigApplyConfiguration struct {
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
Token *string `json:"token,omitempty"`
EncryptedToken []byte `json:"encryptedToken,omitempty"`
Path *string `json:"path,omitempty"`
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
Path *string `json:"path,omitempty"`
}
// GitLabRepositoryConfigApplyConfiguration constructs a declarative configuration of the GitLabRepositoryConfig type for use with
@@ -36,24 +34,6 @@ func (b *GitLabRepositoryConfigApplyConfiguration) WithBranch(value string) *Git
return b
}
// WithToken sets the Token field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Token field is set to the value of the last call.
func (b *GitLabRepositoryConfigApplyConfiguration) WithToken(value string) *GitLabRepositoryConfigApplyConfiguration {
b.Token = &value
return b
}
// WithEncryptedToken adds the given value to the EncryptedToken field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the EncryptedToken field.
func (b *GitLabRepositoryConfigApplyConfiguration) WithEncryptedToken(values ...byte) *GitLabRepositoryConfigApplyConfiguration {
for i := range values {
b.EncryptedToken = append(b.EncryptedToken, values[i])
}
return b
}
// WithPath sets the Path field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Path field is set to the value of the last call.
@@ -7,12 +7,10 @@ package v0alpha1
// GitRepositoryConfigApplyConfiguration represents a declarative configuration of the GitRepositoryConfig type for use
// with apply.
type GitRepositoryConfigApplyConfiguration struct {
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
TokenUser *string `json:"tokenUser,omitempty"`
Token *string `json:"token,omitempty"`
EncryptedToken []byte `json:"encryptedToken,omitempty"`
Path *string `json:"path,omitempty"`
URL *string `json:"url,omitempty"`
Branch *string `json:"branch,omitempty"`
TokenUser *string `json:"tokenUser,omitempty"`
Path *string `json:"path,omitempty"`
}
// GitRepositoryConfigApplyConfiguration constructs a declarative configuration of the GitRepositoryConfig type for use with
@@ -45,24 +43,6 @@ func (b *GitRepositoryConfigApplyConfiguration) WithTokenUser(value string) *Git
return b
}
// WithToken sets the Token field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Token field is set to the value of the last call.
func (b *GitRepositoryConfigApplyConfiguration) WithToken(value string) *GitRepositoryConfigApplyConfiguration {
b.Token = &value
return b
}
// WithEncryptedToken adds the given value to the EncryptedToken field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the EncryptedToken field.
func (b *GitRepositoryConfigApplyConfiguration) WithEncryptedToken(values ...byte) *GitRepositoryConfigApplyConfiguration {
for i := range values {
b.EncryptedToken = append(b.EncryptedToken, values[i])
}
return b
}
// WithPath sets the Path field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Path field is set to the value of the last call.
@@ -9,8 +9,6 @@ package v0alpha1
type WebhookStatusApplyConfiguration struct {
ID *int64 `json:"id,omitempty"`
URL *string `json:"url,omitempty"`
Secret *string `json:"secret,omitempty"`
EncryptedSecret []byte `json:"encryptedSecret,omitempty"`
SubscribedEvents []string `json:"subscribedEvents,omitempty"`
LastEvent *int64 `json:"lastEvent,omitempty"`
}
@@ -37,24 +35,6 @@ func (b *WebhookStatusApplyConfiguration) WithURL(value string) *WebhookStatusAp
return b
}
// WithSecret sets the Secret field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Secret field is set to the value of the last call.
func (b *WebhookStatusApplyConfiguration) WithSecret(value string) *WebhookStatusApplyConfiguration {
b.Secret = &value
return b
}
// WithEncryptedSecret adds the given value to the EncryptedSecret field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the EncryptedSecret field.
func (b *WebhookStatusApplyConfiguration) WithEncryptedSecret(values ...byte) *WebhookStatusApplyConfiguration {
for i := range values {
b.EncryptedSecret = append(b.EncryptedSecret, values[i])
}
return b
}
// WithSubscribedEvents adds the given value to the SubscribedEvents field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the SubscribedEvents field.
+5 -2
View File
@@ -19,7 +19,7 @@ SecureValueSpec: {
// Minimum and maximum lengths in bytes.
// +k8s:validation:minLength=1
// +k8s:validation:maxLength=24576
// +optional
// +optional
value?: #ExposedSecureValue
// When using a third-party keeper, the `ref` is used to reference a value inside the remote storage.
@@ -41,13 +41,16 @@ SecureValueSpec: {
// +k8s:validation:maxItems=64
// +k8s:validation:uniqueItems=true
// +listType=atomic
// +optional
// +optional
decrypters?: [...string] & list.UniqueItems() & list.MaxItems(64)
}
SecureValueStatus: {
// Version of the secure value. Cannot be set.
// +optional
version: int64 & >=0
// External ID where the secret is stored. Cannot be set.
// +optional
externalID: string
}
@@ -22,10 +22,13 @@ func NewSecureValuestatusOperatorState() *SecureValuestatusOperatorState {
// +k8s:openapi-gen=true
type SecureValueStatus struct {
// Version of the secure value. Cannot be set.
// +optional
Version int64 `json:"version"`
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
OperatorStates map[string]SecureValuestatusOperatorState `json:"operatorStates,omitempty"`
// External ID where the secret is stored. Cannot be set.
// +optional
ExternalID string `json:"externalID"`
// additionalFields is reserved for future use
@@ -633,9 +633,10 @@ func schema_pkg_apis_secret_v1beta1_SecureValueStatus(ref common.ReferenceCallba
Properties: map[string]spec.Schema{
"version": {
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"integer"},
Format: "int64",
Description: "Version of the secure value. Cannot be set.",
Default: 0,
Type: []string{"integer"},
Format: "int64",
},
},
"operatorStates": {
@@ -655,9 +656,10 @@ func schema_pkg_apis_secret_v1beta1_SecureValueStatus(ref common.ReferenceCallba
},
"externalID": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
Description: "External ID where the secret is stored. Cannot be set.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"additionalFields": {
@@ -676,7 +678,6 @@ func schema_pkg_apis_secret_v1beta1_SecureValueStatus(ref common.ReferenceCallba
},
},
},
Required: []string{"version"},
},
},
Dependencies: []string{
+1 -1
View File
@@ -121,7 +121,7 @@ For an example of the `IsDisabled` method and custom initialization code when th
## Run Wire (generate code)
Running `make run` calls `make gen-go` on the first run. The `gen-go` in turn calls the Wire binary and generates the code in [`wire_gen.go`](/pkg/server/wire_gen.go). The Wire binary is installed using [`bingo`](https://github.com/bwplotka/bingo) which downloads and installs all the tools needed, including the Wire binary at the specified version.
Running `make run` calls `make gen-go` on the first run. The `gen-go` in turn calls the Wire binary and generates the code in [`wire_gen.go`](/pkg/server/wire_gen.go). The Wire binary is installed using `go tool` which downloads and installs all the tools needed, including the Wire binary at the specified version.
## OSS vs. Enterprise
@@ -25,7 +25,6 @@
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
@@ -42,7 +41,7 @@
"steps": [
{
"color": "green",
"value": null
"value": 0
},
{
"color": "red",
@@ -156,11 +155,19 @@
"x": -0.8229007633587786,
"y": 0.27741935483870966
},
"sourceOriginal": {
"x": 1223.8335877862596,
"y": 300
},
"target": {
"x": 1.0112359550561798,
"y": -0.012987012987012988
},
"targetName": "Element 20",
"targetOriginal": {
"x": 990,
"y": 474
},
"vertices": [
{
"x": -0.18181818181818182,
@@ -191,11 +198,19 @@
"x": -0.3068702290076336,
"y": -0.47419354838709676
},
"sourceOriginal": {
"x": 1401.090076335878,
"y": 533
},
"target": {
"x": 1.0112359550561798,
"y": -0.4805194805194805
},
"targetName": "Element 20",
"targetOriginal": {
"x": 990,
"y": 492
},
"vertices": [
{
"x": 0.415,
@@ -227,11 +242,19 @@
"x": 0.7903930131004366,
"y": -0.36935483870967745
},
"sourceOriginal": {
"x": 1778,
"y": 500.5
},
"target": {
"x": 1.0120481927710843,
"y": 0.024691358024691357
},
"targetName": "Element 35",
"targetOriginal": {
"x": 1523,
"y": 177
},
"vertices": [
{
"x": -0.1607843137254902,
@@ -518,11 +541,19 @@
"x": 0.8876404494382022,
"y": 0.2597402597402597
},
"sourceOriginal": {
"x": 741,
"y": 356.5
},
"target": {
"x": -0.9775280898876404,
"y": -0.012987012987012988
},
"targetName": "Element 19"
"targetName": "Element 19",
"targetOriginal": {
"x": 813,
"y": 358
}
}
],
"constraint": {
@@ -614,11 +645,19 @@
"x": 0.8876404494382022,
"y": 0.2597402597402597
},
"sourceOriginal": {
"x": 741,
"y": 239.5
},
"target": {
"x": -0.9887640449438202,
"y": -0.06493506493506493
},
"targetName": "Element 18"
"targetName": "Element 18",
"targetOriginal": {
"x": 812,
"y": 242
}
}
],
"constraint": {
@@ -673,11 +712,19 @@
"x": -0.2247191011235955,
"y": -0.4805194805194805
},
"sourceOriginal": {
"x": 342,
"y": 395
},
"target": {
"x": 0.011235955056179775,
"y": 0.922077922077922
},
"targetName": "Element 12"
"targetName": "Element 12",
"targetOriginal": {
"x": 343,
"y": 458
}
},
{
"color": {
@@ -693,11 +740,19 @@
"x": 0.7752808988764045,
"y": 0.5194805194805194
},
"sourceOriginal": {
"x": 431,
"y": 356.5
},
"target": {
"x": -1.0833333333333333,
"y": 0.020618556701030927
},
"targetName": "Element 15"
"targetName": "Element 15",
"targetOriginal": {
"x": 526,
"y": 356
}
}
],
"constraint": {
@@ -888,11 +943,19 @@
"x": 1,
"y": 0.5
},
"sourceOriginal": {
"x": 431,
"y": 337.25
},
"target": {
"x": -1.0112359550561798,
"y": -0.012987012987012988
},
"targetName": "Element 16",
"targetOriginal": {
"x": 562,
"y": 240
},
"vertices": [
{
"x": 0.42748091603053434,
@@ -963,11 +1026,19 @@
"x": 0,
"y": -1
},
"sourceOriginal": {
"x": 150,
"y": 223
},
"target": {
"x": -0.5168539325842697,
"y": 1
},
"targetName": "Element 6",
"targetOriginal": {
"x": 296,
"y": 318
},
"vertices": [
{
"x": 0,
@@ -996,11 +1067,19 @@
"x": 0,
"y": 1
},
"sourceOriginal": {
"x": 150,
"y": 148
},
"target": {
"x": -1.0112359550561798,
"y": -0.012987012987012988
},
"targetName": "Element 30",
"targetOriginal": {
"x": 563,
"y": 82
},
"vertices": [
{
"x": 0,
@@ -1062,11 +1141,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 216,
"y": 356.5
},
"target": {
"x": -1.0112359550561798,
"y": -0.012987012987012988
},
"targetName": "Element 6"
"targetName": "Element 6",
"targetOriginal": {
"x": 252,
"y": 357
}
}
],
"constraint": {
@@ -1121,11 +1208,19 @@
"x": 0,
"y": -1
},
"sourceOriginal": {
"x": 342,
"y": 223
},
"target": {
"x": 0.011235955056179775,
"y": 1.025974025974026
},
"targetName": "Element 6"
"targetName": "Element 6",
"targetOriginal": {
"x": 343,
"y": 317
}
}
],
"constraint": {
@@ -1218,11 +1313,19 @@
"x": -1,
"y": 0
},
"sourceOriginal": {
"x": 38,
"y": 493.5
},
"target": {
"x": -1.0224719101123596,
"y": 0.013333333333333334
},
"targetName": "Element 9",
"targetOriginal": {
"x": 59,
"y": 185
},
"vertices": [
{
"x": -0.9523809523809523,
@@ -1399,11 +1502,19 @@
"x": -1,
"y": 0
},
"sourceOriginal": {
"x": 563,
"y": 473.5
},
"target": {
"x": 1.0224719101123596,
"y": -0.45454545454545453
},
"targetName": "Element 6",
"targetOriginal": {
"x": 433,
"y": 374
},
"vertices": [
{
"x": 0.5692307692307692,
@@ -1468,11 +1579,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 989,
"y": 239.5
},
"target": {
"x": -0.96,
"y": 0.4583333333333333
},
"targetName": "Element 2",
"targetOriginal": {
"x": 1392,
"y": 487
},
"vertices": [
{
"x": 0.3349875930521092,
@@ -1506,11 +1625,19 @@
"x": 1,
"y": -0.5
},
"sourceOriginal": {
"x": 989,
"y": 258.75
},
"target": {
"x": -0.9761904761904762,
"y": 0.5263157894736842
},
"targetName": "Element 34",
"targetOriginal": {
"x": 1138,
"y": 290
},
"vertices": [
{
"x": 0.5033557046979866,
@@ -1612,11 +1739,19 @@
"x": -1,
"y": 0
},
"sourceOriginal": {
"x": 811,
"y": 473.5
},
"target": {
"x": 1.0112359550561798,
"y": 0.012987012987012988
},
"targetName": "Element 17"
"targetName": "Element 17",
"targetOriginal": {
"x": 742,
"y": 473
}
}
],
"constraint": {
@@ -1671,11 +1806,19 @@
"x": 1,
"y": -0.5
},
"sourceOriginal": {
"x": 1025.5,
"y": 382.25
},
"target": {
"x": -1.0238095238095237,
"y": 0.10526315789473684
},
"targetName": "Element 33"
"targetName": "Element 33",
"targetOriginal": {
"x": 1136,
"y": 384
}
},
{
"color": {
@@ -1691,11 +1834,19 @@
"x": 1,
"y": 0.5
},
"sourceOriginal": {
"x": 1025.5,
"y": 333.75
},
"target": {
"x": -1,
"y": 0.05263157894736842
},
"targetName": "Element 34",
"targetOriginal": {
"x": 1137,
"y": 299
},
"vertices": [
{
"x": 0.5246636771300448,
@@ -1721,11 +1872,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 1025.5,
"y": 358
},
"target": {
"x": -0.98,
"y": -0.10416666666666667
},
"targetName": "Element 2",
"targetOriginal": {
"x": 1391,
"y": 514
},
"vertices": [
{
"x": 0.7564979480164159,
@@ -1792,11 +1951,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 742,
"y": 81.5
},
"target": {
"x": -1.0112359550561798,
"y": -0.012987012987012988
},
"targetName": "Element 31"
"targetName": "Element 31",
"targetOriginal": {
"x": 811,
"y": 82
}
},
{
"color": {
@@ -1818,11 +1985,19 @@
"x": 0,
"y": 1
},
"sourceOriginal": {
"x": 653,
"y": 43
},
"target": {
"x": -0.029411764705882353,
"y": 1
},
"targetName": "Element 38",
"targetOriginal": {
"x": 1673,
"y": 249
},
"vertices": [
{
"x": 0,
@@ -1892,11 +2067,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 990,
"y": 81.5
},
"target": {
"x": -0.9939759036144579,
"y": 0.5061728395061729
},
"targetName": "Element 35",
"targetOriginal": {
"x": 1190,
"y": 138
},
"vertices": [
{
"x": 0.48,
@@ -1961,11 +2144,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 1221,
"y": 386
},
"target": {
"x": 1.0337078651685394,
"y": 0.45454545454545453
},
"targetName": "Element 20",
"targetOriginal": {
"x": 992,
"y": 456
},
"vertices": [
{
"x": -0.05240174672489083,
@@ -2067,11 +2258,19 @@
"x": -1,
"y": 0
},
"sourceOriginal": {
"x": 1189,
"y": 179
},
"target": {
"x": 0.19444444444444445,
"y": 0.9896907216494846
},
"targetName": "Element 29",
"targetOriginal": {
"x": 1011,
"y": 310
},
"vertices": [
{
"x": 1,
@@ -2169,11 +2368,19 @@
"x": -1,
"y": 0
},
"sourceOriginal": {
"x": 1574,
"y": 308
},
"target": {
"x": -0.4977168949771689,
"y": 1.017467248908297
},
"targetName": "Element 36",
"targetOriginal": {
"x": 1450,
"y": 384
},
"vertices": [
{
"x": 1,
@@ -2208,7 +2415,7 @@
},
"showAdvancedTypes": true
},
"pluginVersion": "11.1.0-pre",
"pluginVersion": "12.1.0-pre",
"targets": [
{
"datasource": {
@@ -2236,7 +2443,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "#6ED0E0",
@@ -2260,7 +2468,7 @@
"h": 11,
"w": 15,
"x": 5,
"y": 22
"y": 36
},
"id": 4,
"options": {
@@ -2320,11 +2528,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 216,
"y": 192
},
"target": {
"x": -1.02020202020202,
"y": 0.041666666666666664
},
"targetName": "Element 2"
"targetName": "Element 2",
"targetOriginal": {
"x": 326,
"y": 191
}
}
],
"constraint": {
@@ -2377,11 +2593,19 @@
"x": 1,
"y": 1
},
"sourceOriginal": {
"x": 426,
"y": 168
},
"target": {
"x": -0.9797979797979798,
"y": 0
},
"targetName": "Element 3"
"targetName": "Element 3",
"targetOriginal": {
"x": 549,
"y": 93
}
},
{
"color": {
@@ -2398,11 +2622,19 @@
"x": 1,
"y": -1
},
"sourceOriginal": {
"x": 426,
"y": 216
},
"target": {
"x": -1,
"y": 0.041666666666666664
},
"targetName": "Element 4"
"targetName": "Element 4",
"targetOriginal": {
"x": 549,
"y": 297
}
}
],
"constraint": {
@@ -2455,11 +2687,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 647,
"y": 93
},
"target": {
"x": -0.9595959595959596,
"y": -0.041666666666666664
},
"targetName": "Element 11"
"targetName": "Element 11",
"targetOriginal": {
"x": 799,
"y": 94
}
}
],
"constraint": {
@@ -2512,11 +2752,19 @@
"x": 1,
"y": 0
},
"sourceOriginal": {
"x": 648,
"y": 298
},
"target": {
"x": -0.9797979797979798,
"y": 0.041666666666666664
},
"targetName": "Element 5"
"targetName": "Element 5",
"targetOriginal": {
"x": 798,
"y": 299
}
}
],
"constraint": {
@@ -2838,7 +3086,7 @@
},
"showAdvancedTypes": false
},
"pluginVersion": "11.1.0-pre",
"pluginVersion": "12.1.0-pre",
"targets": [
{
"csvContent": "gateway, product-details, product-reviews, reviews-ratings, details-checkout\n100, 56, 44, 22, 28",
@@ -2873,7 +3121,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "blue",
@@ -2897,7 +3146,7 @@
"h": 15,
"w": 15,
"x": 5,
"y": 33
"y": 47
},
"id": 2,
"options": {
@@ -2949,6 +3198,10 @@
"field": "server_database2",
"fixed": "white"
},
"direction": {
"field": "server_database2",
"mode": "field"
},
"path": "straight",
"size": {
"fixed": 2,
@@ -2959,17 +3212,29 @@
"x": -1,
"y": -1
},
"sourceOriginal": {
"x": 468,
"y": 125
},
"target": {
"x": 0.030303030303030304,
"y": 0
},
"targetName": "Element 11"
"targetName": "Element 11",
"targetOriginal": {
"x": 285,
"y": 170
}
},
{
"color": {
"field": "server_database",
"fixed": "white"
},
"direction": {
"field": "server_database",
"mode": "field"
},
"path": "straight",
"size": {
"fixed": 2,
@@ -2980,17 +3245,29 @@
"x": -1,
"y": -1
},
"sourceOriginal": {
"x": 468,
"y": 125
},
"target": {
"x": 0.09090909090909091,
"y": 0.16666666666666666
},
"targetName": "Element 12"
"targetName": "Element 12",
"targetOriginal": {
"x": 390,
"y": 264
}
},
{
"color": {
"field": "server_region",
"fixed": "white"
},
"direction": {
"field": "server_region",
"mode": "field"
},
"path": "straight",
"size": {
"fixed": 2,
@@ -3001,11 +3278,19 @@
"x": 1,
"y": -1
},
"sourceOriginal": {
"x": 588.9921875,
"y": 125
},
"target": {
"x": -0.2727272727272727,
"y": 0.4166666666666667
},
"targetName": "Element 13"
"targetName": "Element 13",
"targetOriginal": {
"x": 658,
"y": 259
}
}
],
"constraint": {
@@ -3050,6 +3335,10 @@
"field": "database_server",
"fixed": "white"
},
"direction": {
"field": "database_server",
"mode": "field"
},
"path": "straight",
"size": {
"fixed": 2,
@@ -3060,11 +3349,19 @@
"x": 1,
"y": 1
},
"sourceOriginal": {
"x": 328,
"y": 367
},
"target": {
"x": -0.15151515151515152,
"y": -0.16666666666666666
},
"targetName": "Element 12"
"targetName": "Element 12",
"targetOriginal": {
"x": 386,
"y": 268
}
}
],
"constraint": {
@@ -3109,6 +3406,10 @@
"field": "region_server",
"fixed": "white"
},
"direction": {
"field": "region_server",
"mode": "field"
},
"path": "straight",
"size": {
"fixed": 2,
@@ -3119,11 +3420,19 @@
"x": -1,
"y": 1
},
"sourceOriginal": {
"x": 699,
"y": 365
},
"target": {
"x": -0.21212121212121213,
"y": 0.08333333333333333
},
"targetName": "Element 13"
"targetName": "Element 13",
"targetOriginal": {
"x": 659,
"y": 263
}
}
],
"constraint": {
@@ -3212,6 +3521,10 @@
"field": "database2_server",
"fixed": "white"
},
"direction": {
"field": "database2_server",
"mode": "field"
},
"path": "straight",
"size": {
"fixed": 2,
@@ -3222,11 +3535,19 @@
"x": 1,
"y": 1
},
"sourceOriginal": {
"x": 172,
"y": 191
},
"target": {
"x": -0.3333333333333333,
"y": 0
},
"targetName": "Element 11"
"targetName": "Element 11",
"targetOriginal": {
"x": 279,
"y": 170
}
}
],
"constraint": {
@@ -3605,7 +3926,7 @@
},
"showAdvancedTypes": false
},
"pluginVersion": "11.1.0-pre",
"pluginVersion": "12.1.0-pre",
"targets": [
{
"csvContent": "database_server, server_database, server_region, region_server, database2_server, server_database2\n10, 53, 35, 12, 22, 81",
@@ -3620,9 +3941,9 @@
"type": "canvas"
}
],
"preload": false,
"refresh": "",
"revision": 1,
"schemaVersion": 39,
"schemaVersion": 41,
"tags": ["gdev", "panel-tests", "canvas"],
"templating": {
"list": []
@@ -3631,11 +3952,9 @@
"from": "now-6h",
"to": "now"
},
"timeRangeUpdatedDuringEditOrView": false,
"timepicker": {},
"timezone": "",
"title": "Panel Tests - Canvas Connection Examples",
"uid": "Pu8lwQAVz",
"version": 6,
"weekStart": ""
"version": 15
}
@@ -30,6 +30,16 @@ Read on for an overview on how to get started with plugins:
- Configure backend communication between installed plugins.
- Improve security by isolating plugins with the Plugin Frontend Sandbox.
## Types of plugins
Grafana supports three types of plugins:
- [Panels](/grafana/plugins/panel-plugins) - These plugins make it easy to create and add any kind of visualization, to show your data, or improve your favorite dashboards.
- [Data sources](/grafana/plugins/data-source-plugins) - These plugins allow you to pull data from various data sources such as databases, APIs, log files, and so on, and display it in the form of graphs, charts, and dashboards in Grafana.
- [Apps](/grafana/plugins/app-plugins) - These plugins enable the bundling of data sources, panels, dashboards, and Grafana pages into a cohesive experience.
Read more in [Types of plugins](plugin-types).
## Plugin catalog
The Grafana plugin catalog allows you to browse and manage plugins from within Grafana. Only Grafana server administrators and Organization administrators can access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions](../roles-and-permissions/).
@@ -40,41 +50,12 @@ The following access rules apply depending on the user role:
- If you are a **Server Admin**, you can't configure app plugins, but you can install, uninstall, or update them.
- If you are both **Org Admin** and **Server Admin**, you can configure app plugins and also install, uninstall, or update them.
{{< admonition type="note" >}}
The Grafana plugin catalog is designed to work with a single Grafana server instance only. Support for Grafana clusters is planned for future Grafana releases.
{{< /admonition >}}
<div class="medium-6 columns">
<video width="700" height="600" controls>
<source src="/static/assets/videos/plugins-catalog-install-9.2.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
_Video shows the Plugin catalog in a previous version of Grafana._
{{< admonition type="note" >}}
If required, the Grafana plugin catalog can be disabled using the `plugin_admin_enabled` flag in the [configuration](../../setup-grafana/configure-grafana/#plugin_admin_enabled) file.
{{< /admonition >}}
<a id="#plugin-catalog-entry"></a>
### Types of plugins
Grafana supports three types of plugins:
- [Panels](/grafana/plugins/panel-plugins) - These plugins make it easy to create and add any kind of panel, to show your data, or improve your favorite dashboards.
- [Data sources](/grafana/plugins/data-source-plugins) - These plugins allow you to pull data from various data sources such as databases, APIs, log files, and so on, and display it in the form of graphs, charts, and dashboards in Grafana.
- [Apps](/grafana/plugins/app-plugins) - These plugins enable the bundling of data sources, panels, dashboards, and Grafana pages into a cohesive experience.
Read more in [Types of plugins](plugin-types).
### Browse plugins
To browse for available plugins:
1. While logged into Grafana as an administrator, click **Administration > Plugins and data > Plugins** in the side menu to view installed and available plugins.
1. Use the search to filter based on name, keywords, organization and other metadata.
1. Use the search box to filter based on name, keywords, organization and other metadata.
1. Click the **Data sources**, **Panels**, or **Applications** buttons to filter by plugin type.
## Manage your plugins
@@ -126,7 +107,7 @@ Plugin signature verification, also known as _signing_, is a security measure to
You can relocate app plugin pages to customize the navigation menu structure, as explained in [Customize navigation placement of plugin pages](customize-nav-bar).
### Integrate plugins
### Allow plugin backend communication
You can configure your Grafana instance to let the frontends of installed plugins directly communicate locally with the backends of other installed plugins. See how in [Configure backend communication between installed plugins](plugin-integrate).
@@ -134,6 +115,13 @@ You can configure your Grafana instance to let the frontends of installed plugin
You can use the [Plugin Frontend Sandbox](plugin-frontend-sandbox) to securely isolate plugin frontend code from the main Grafana application.
When enabled, plugins run in a separate JavaScript context, which provides several security benefits:
- Prevents plugins from modifying parts of the Grafana interface outside their designated areas
- Stops plugins from interfering with other plugins functionality
- Protects core Grafana features from being altered by plugins
- Prevents plugins from modifying global browser objects and behaviors
### Learn more
- [Browse available plugins](/grafana/plugins)
@@ -45,6 +45,12 @@ Data source plugins communicate with external sources of data and return the dat
Use data source plugins when you want to query data from external or third-party systems.
### Managing access for data source plugins
Administrators can grant access to data source plugins with [LBAC](/docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/teamlbac/).
Label Based Access Control (LBAC) customizes access rights based on team memberships, ensuring that users only query data relevant to their assigned permissions.
## App plugins
Applications, or app plugins, bundle data sources and panels to provide a cohesive experience, such as the [Zabbix](/grafana/plugins/alexanderzobnin-zabbix-app) app.
@@ -57,7 +63,9 @@ Use app plugins when you want an out-of-the-box monitoring experience.
Customize access to app plugins with [RBAC](/docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/rbac-for-app-plugins/).
By default, the Viewer, Editor and Admin roles have access to all app plugins that their Organization role allows them to access. Access is granted by the `fixed:plugins.app:reader` role.
By default, the Viewer, Editor and Admin roles have access to all app plugins that their Organization role allows them to access.
Access is granted by the `fixed:plugins.app:reader` role.
{{< admonition type="note" >}}
To prevent users from seeing an app plugin, refer to [these permissions scenarios](/docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/#prevent-viewers-from-accessing-an-app-plugin).
@@ -81,12 +81,16 @@ Folders help you organize and group dashboards, which is useful when you have ma
1. Enter a unique name and click **Create**.
Folder names can't include underscores (\_) or percentage signs (%), as it interferes with the search functionality.
Also, alerts can't be placed in folders with slashes (\ /) in the name. If you want to place alerts in the folder, don't use slashes in the folder name.
When you nest folders, you can do so up to four levels deep.
When you save a dashboard, you can optionally select a folder to save the dashboard in.
{{< admonition type="note" >}}
Alerts can't be placed in folders with slashes (\ /) in the name. If you wish to place alerts in the folder, don't use slashes in the folder name.
{{< /admonition >}}
**To edit the name of a folder:**
@@ -688,7 +688,7 @@
},
"languages": {
"type": "array",
"description": "The list of languages supported by the plugin. Each entry should be a locale identifier in the format `language-COUNTRY` (e.g., `en-US`, `fr-FR`, `es-ES`).",
"description": "The list of languages supported by the plugin. Each entry should be a locale identifier in the format `language-COUNTRY` (for example `en-US`, `fr-FR`, `es-ES`).",
"items": {
"type": "string"
}
@@ -481,12 +481,19 @@ You can style the selected connection using the following options:
- **Color** - Set the connection color.
- **Size** - Control the size of the connection by entering a number in the **Value** field.
- **Radius** - Add curve to the connection by entering a value to represent the degree.
- **Arrow Direction** - Control the appearance of the arrow head. Choose from:
- **Direction** - Control the appearance of the arrow head. Choose your source from **Fixed** or **Field**. The default value is **Forward** regardless of the source type.
If the direction source is **Fixed**, choose from:
- **Forward** - The arrow head points in the direction in which the connection was drawn.
- **Reverse** - The arrow head points in the opposite direction of which the connection was drawn.
- **Both** - Adds arrow heads to both ends of the connection.
- **None** - Removes the arrow head.
If the direction source is **Field**, select a field that contains numeric values:
- **Positive values** - Display forward arrows.
- **Negative values** - Display reverse arrows.
- **Zero** - Display no arrow heads.
- **Line style** - Choose from the following line styles: **Solid**, **Dashed**, and **Dotted**.
### Standard options
@@ -281,7 +281,7 @@ The markers layer allows you to display data points as different marker shapes s
| Symbol Horizontal Align | Configures the horizontal alignment of the symbol relative to the data point. Note that the symbol's rotation angle is applied first around the data point, then the horizontal alignment is applied relative to the rotation of the symbol. |
| Color | Configures the color of the markers. The default `Fixed color` sets all markers to a specific color. There is also an option to have conditional colors depending on the selected field data point values and the color scheme set in the `Standard options` section. |
| Fill opacity | Configures the transparency of each marker. |
| Rotation angle | Configures the rotation angle of each marker. The default is `Fixed value`, which makes all markers rotate to the same angle regardless of the data; however, there is also an option to set the rotation of the markers based on data corresponding to a selected field. |
| Rotation angle | Configures the rotation angle of each marker in degrees. The default is `Fixed value`, which makes all markers rotate to the same angle regardless of the data; however, there is also an option to set the rotation of the markers based on data corresponding to a selected field. |
| Text label | Configures a text label for each marker. |
| Show legend | Allows you to toggle the legend for the layer. |
| Display tooltip | Allows you to toggle tooltips for the layer. |
@@ -448,7 +448,7 @@ You can convert node graph data to a network layer:
| Symbol | Allows you to choose the symbol, icon, or graphic to aid in providing additional visual context to your data. Choose from assets that are included with Grafana such as simple symbols or the Unicon library. You can also specify a URL containing an image asset. The image must be a scalable vector graphic (SVG). |
| Color | Configures the color of the nodes. The default `Fixed color` sets all nodes to a specific color. There is also an option to have conditional colors depending on the selected field data point values and the color scheme set in the `Standard options` section. |
| Fill opacity | Configures the transparency of each node. |
| Rotation angle | Configures the rotation angle of each node. The default is `Fixed value`, which makes all nodes rotate to the same angle regardless of the data; however, there is also an option to set the rotation of the nodes based on data corresponding to a selected field. |
| Rotation angle | Configures the rotation angle of each node in degrees. The default is `Fixed value`, which makes all nodes rotate to the same angle regardless of the data; however, there is also an option to set the rotation of the nodes based on data corresponding to a selected field. |
| Text label | Configures a text label for each node. |
<!-- prettier-ignore-end -->
@@ -198,6 +198,14 @@ List of enabled loggers.
Keep dashboard content in the logs (request or response fields). This can significantly increase the size of your logs.
### log_datasource_query_request_body
Whether to record data source queries' request body. This can significantly increase the size of your logs. Enabled by default.
### log_datasource_query_response_body
Whether to record data source queries' response body. This can significantly increase the size of your logs. Enabled by default.
### verbose
Log all requests and keep requests and responses body. This can significantly increase the size of your logs.
@@ -371,6 +371,10 @@ enabled = false
loggers = file
# Keep dashboard content in the logs (request or response fields); this can significantly increase the size of your logs.
log_dashboard_content = false
# Whether to record data source queries' request body. This can significantly increase the size of your logs. Enabled by default.
log_datasource_query_request_body = true
# Whether to record data source queries' response body. This can significantly increase the size of your logs. Enabled by default.
log_datasource_query_response_body = true
# Keep requests and responses body; this can significantly increase the size of your logs.
verbose = false
# Write an audit log for every status code.
@@ -143,7 +143,7 @@ If set to `true`, the Grafana server redirects requests that have a Host-header
Example:
```toml
[sever]
[server]
# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true
@@ -68,7 +68,7 @@ Grafana currently follows a monthly release schedule. Below are the planned rele
- The schedule above outlines how we plan release dates. However, unforeseen events and circumstances may cause dates to change.
- High severity security and feature degradation incidents will result in ad-hoc releases that are not scheduled ahead of time.
- Patching releases are for the current (last released) minor version of Grafana. Additional older versions of Grafana may be included if there is a critical bug or security vulnerability that needs to be patched.
- Patching releases are for all supported minor versions of Grafana. Each supported minor version receives patch releases with bug fixes and security patches until its end of life.
- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident.
## Grafana security releases: improved version naming convention
@@ -96,47 +96,63 @@ For self-managed Grafana (both Enterprise and OSS), the support for versions fol
- Each minor release is supported for 9 months after its release date
- The last minor release of a major version receives extended support for 15 months after its release date
- Support levels change as new versions are released:
- **Full Support**: The most recently released major/minor version receive full support including new features, bug fixes, and security patches
- **Security & Critical Bugs Only**: Versions that are outside of the most recently released major/minor version, but still within their support period, receive only security patches and critical bug fixes
- **Full Support**: The current major version receives new features through new minor releases
- **Patch Support**: Individual minor versions receive patch releases (bug fixes and security patches) until end of life
- **Not Supported**: Versions beyond their support period receive no updates
Here is an overview of version support through 2026:
| **Version** | **Release date** | **Support end date** | **Support level** |
| ------------------------- | ------------------ | -------------------- | ----------------------------- |
| 10.2.x | October 24, 2023 | July 24, 2024 | Not Supported |
| 10.3.x | January 23, 2024 | October 23, 2024 | Not Supported |
| 10.4.x (Last minor of 10) | March 5, 2024 | June 5, 2025 | Not Supported |
| 11.0.x | May 14, 2024 | February 14, 2025 | Not Supported |
| 11.1.x | June 25, 2024 | April 23, 2025 | Not Supported |
| 11.2.x | August 27, 2024 | May 27, 2025 | Not Supported |
| 11.3.x | October 22, 2024 | July 22, 2025 | Not Supported |
| 11.4.x | December 5, 2024 | September 5, 2025 | Security & Critical Bugs Only |
| 11.5.x | January 28, 2025 | October 28, 2025 | Security & Critical Bugs Only |
| 11.6.x (Last minor of 11) | March 25, 2025 | June 25, 2026 | Security & Critical Bugs Only |
| 12.0.x | May 5, 2025 | February 5, 2026 | Security & Critical Bugs Only |
| 12.1.x | July 22, 2025 | April 22, 2026 | Full Support until next minor |
| 12.2.x | September 23, 2025 | June 23, 2026 | Yet to be released |
| 12.3.x | November 18, 2025 | August 18, 2026 | Yet to be released |
| **Version** | **Release date** | **Support end date** | **Support level** |
| ------------------------- | ------------------ | -------------------- | ------------------ |
| 10.2.x | October 24, 2023 | July 24, 2024 | Not Supported |
| 10.3.x | January 23, 2024 | October 23, 2024 | Not Supported |
| 10.4.x (Last minor of 10) | March 5, 2024 | June 5, 2025 | Not Supported |
| 11.0.x | May 14, 2024 | February 14, 2025 | Not Supported |
| 11.1.x | June 25, 2024 | April 23, 2025 | Not Supported |
| 11.2.x | August 27, 2024 | May 27, 2025 | Not Supported |
| 11.3.x | October 22, 2024 | July 22, 2025 | Not Supported |
| 11.4.x | December 5, 2024 | September 5, 2025 | Patch Support |
| 11.5.x | January 28, 2025 | October 28, 2025 | Patch Support |
| 11.6.x (Last minor of 11) | March 25, 2025 | June 25, 2026 | Patch Support |
| 12.0.x | May 5, 2025 | February 5, 2026 | Patch Support |
| 12.1.x | July 22, 2025 | April 22, 2026 | Patch Support |
| 12.2.x | September 23, 2025 | June 23, 2026 | Yet to be released |
| 12.3.x | November 18, 2025 | August 18, 2026 | Yet to be released |
## How are these versions supported?
The level of support changes as new versions of Grafana are released. Here are the key details:
Self-managed Grafana follows semantic-like versioning (MAJOR.MINOR.PATCH). Here's how different types of releases work:
- **Full Support**:
- All new features
- All bug fixes
- Security patches
- Regular updates
- **Major releases** (e.g., 12.0.0, 13.0.0):
- Awesome new features as well as significant architectural improvements and modernizations
- May include breaking changes that require migration steps
- Released once per year
- **Security & Critical Bugs Only**:
- Security vulnerability patches
- Critical bug fixes that cause feature degradation
- No new features
- **Minor releases** (e.g., 12.3.0, 12.4.0):
- Contain new features and enhancements
- Include bug fixes and security patches
- Released every other month
- **Patch releases** (e.g., 12.3.1, 12.3.2):
- **Only** contain bug fixes and security patches
- **No new features** - these wait until the next minor release
- Released monthly
**Support levels by version:**
- **Full Support** (current major version):
- Gets new minor releases with new features approximately every other month
- All minor versions within the major receive patch releases until end of life
- Example: Major 12.x gets new features via 12.1.0, 12.2.0, 12.3.0, etc.
- **Patch Support** (individual minor versions):
- Each minor version receives patch releases (bug fixes and security patches) until end of life
- No new features - these only come with new minor releases
- Example: 12.3.x gets 12.3.1, 12.3.2, etc. with fixes only
- **Not Supported**: Versions beyond their support period receive no updates and should be upgraded.
Keeping all this in mind, users that want to receive the most recent features and all bug fixes should be on the current (most recently released) version of Grafana.
**Example**: When 12.3.0 is released, it includes new features. Subsequent releases like 12.3.1 and 12.3.2 only include bug fixes and security updates. All new features developed after 12.3.0 wait until 12.4.0 is released.
### What is a critical feature degradation?
@@ -34,7 +34,7 @@ test.describe(
const initialBackground = await panelTitle.evaluate((el) => getComputedStyle(el).background);
expect(initialBackground).not.toMatch(/rgba\(0, 0, 0, 0\)/);
await page.getByRole('switch', { name: 'Transparent background' }).click({ force: true });
await page.locator('#transparent-background').click({ force: true });
const transparentBackground = await panelTitle.evaluate((el) => getComputedStyle(el).background);
expect(transparentBackground).toMatch(/rgba\(0, 0, 0, 0\)/);
@@ -95,8 +95,8 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
await dashboardPage
.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Cell options Cell value inspect'))
.first()
.getByRole('switch', { name: 'Cell value inspect' })
.click({ force: true });
.locator('label[for="custom.inspect"]')
.click();
await loremIpsumCell.hover();
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
@@ -133,6 +133,15 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
await displayNameInput.fill('State (renamed)');
await displayNameInput.press('Enter');
expect(page.getByRole('row').nth(0)).toContainText('State (renamed)');
// toggle the "State" column visibility again to hide it again. this confirms that we avoid bugs related to
// array lengths between the fields array and the column widths array.
await hideStateColumnSwitch.click();
expect(page.getByRole('row').nth(0)).not.toContainText('State');
// since the previous assertion is just for the absence of text, let's also confirm that the table is
// actually still on the page and that an error has not been throw.
await waitForTableLoad(page);
});
// we test niche cases for sorting, filtering, pagination, etc. in a unit tests already.
@@ -17,35 +17,35 @@ describe('Geomap layer controls options', () => {
e2e.components.PanelEditor.showZoomField()
.should('be.visible')
.within(() => {
cy.get('input[type="checkbox"]').check({ force: true });
cy.get('input[type="checkbox"]').check({ force: true }).should('be.checked');
});
// Show attribution
e2e.components.PanelEditor.showAttributionField()
.should('be.visible')
.within(() => {
cy.get('input[type="checkbox"]').check({ force: true });
cy.get('input[type="checkbox"]').check({ force: true }).should('be.checked');
});
// Show scale
e2e.components.PanelEditor.showScaleField()
.should('be.visible')
.within(() => {
cy.get('input[type="checkbox"]').check({ force: true });
cy.get('input[type="checkbox"]').check({ force: true }).should('be.checked');
});
// Show measure tool
e2e.components.PanelEditor.showMeasureField()
.should('be.visible')
.within(() => {
cy.get('input[type="checkbox"]').check({ force: true });
cy.get('input[type="checkbox"]').check({ force: true }).should('be.checked');
});
// Show debug
e2e.components.PanelEditor.showDebugField()
.should('be.visible')
.within(() => {
cy.get('input[type="checkbox"]').check({ force: true });
cy.get('input[type="checkbox"]').check({ force: true }).should('be.checked');
});
e2e.components.Panels.Panel.content({ timeout: TIMEOUT })
+6 -13
View File
@@ -8,8 +8,6 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y');
const lodashPlugin = require('eslint-plugin-lodash');
const barrelPlugin = require('eslint-plugin-no-barrel-files');
const reactPlugin = require('eslint-plugin-react');
// FIXME: Remove once eslint-config-grafana is updated to include correct plugin
const hooksPlugin = require('eslint-plugin-react-hooks');
const testingLibraryPlugin = require('eslint-plugin-testing-library');
const unicornPlugin = require('eslint-plugin-unicorn');
@@ -60,17 +58,7 @@ module.exports = [
'public/build-swagger', // swagger build output
],
},
// FIXME: Remove once eslint-config-grafana is updated to include correct plugin
{
name: 'react-hooks-plugin',
plugins: {
'react-hooks': hooksPlugin,
},
},
// Conditionally run the betterer rules if enabled in dev's config
...(enableBettererRules ? bettererConfig : []),
// FIXME: Remove filtering once eslint-config-grafana is updated to include correct plugin
...grafanaConfig.filter(Boolean),
...grafanaConfig,
{
name: 'react/jsx-runtime-rules',
rules: reactPlugin.configs.flat['jsx-runtime'].rules,
@@ -176,6 +164,8 @@ module.exports = [
],
// FIXME: Fix these in follow up PR
'react/no-unescaped-entities': 'off',
// Turn off react-hooks/rules-of-hooks whilst present in betterer
'react-hooks/rules-of-hooks': 'off',
},
},
{
@@ -455,4 +445,7 @@ module.exports = [
],
},
},
// Conditionally run the betterer rules if enabled in dev's config
// Should be last in the config so it can override any temporary disables in here
...(enableBettererRules ? bettererConfig : []),
];
+1 -1
View File
@@ -85,7 +85,7 @@ require (
github.com/googleapis/gax-go/v2 v2.14.2 // @grafana/grafana-backend-group
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1 // @grafana/alerting-backend
github.com/grafana/alerting v0.0.0-20250821181654-10915888e4f0 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // @grafana/identity-access-team
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
+2 -2
View File
@@ -1580,8 +1580,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1 h1:nn19bwMFBGWOvUTxZsdewOfRiXERhKHp0jEira8uQA8=
github.com/grafana/alerting v0.0.0-20250812203446-a38b2187d4e1/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
github.com/grafana/alerting v0.0.0-20250821181654-10915888e4f0 h1:CC+ShIgoLTBsAulRfJH9xF++Us6Y3va3VFbeDeJym2M=
github.com/grafana/alerting v0.0.0-20250821181654-10915888e4f0/go.mod h1:VKxaR93Gff0ZlO2sPcdPVob1a/UzArFEW5zx3Bpyhls=
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ=
github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI=
github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw=
+13
View File
@@ -1095,6 +1095,8 @@ github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGU
github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY=
github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk=
github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo=
github.com/jaegertracing/jaeger v1.67.0 h1:t0BiJZVW9D3Z16y3uHqKzV9bKFTusooTH1Kgr77xF2Q=
github.com/jaegertracing/jaeger v1.67.0/go.mod h1:tE/FEQfybCSdUbBgel51YaCSkc58O+Njih8oTl6j8vw=
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg=
@@ -1268,6 +1270,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.1
github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.121.0/go.mod h1:/+tb7Hkt0i92WFkkfHurT7rXrZ98nxat5AeGmbBYFm4=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.121.0 h1:Q27uarV8CEGLKfG4OJQGFkOKZ49/SDEqTr6BbMOoKmA=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.121.0/go.mod h1:f9MPSdMvooe2s9Zg0WVBJOvg72d9fFikxFt7yniFros=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1 h1:NrjsoVPxI6lmV8jPImDcMeqYh+97Y71f/HB5Sfpfe3I=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1/go.mod h1:AFMryJmht7dZxcAwc2sx/r4gxbriElWw49ugxKp2mcA=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.121.0 h1:I+F6xdXQsiXXdce7yjHN+y4LX5MrZI1kNmhBunJffdA=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.121.0/go.mod h1:cRh3l2emFBwW96dHnlPLr1psbEYjYJmn5qFujOkbfRo=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter v0.124.1 h1:2uQmRiQ7EV7s1slz7fEvAVhJIFTyExnj/4unfw0Era8=
@@ -1280,6 +1284,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcompone
github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.124.1/go.mod h1:jRQpw8VJDmUmyH8ONQ5FmhYEXkaxzMFX6Tei8N4GDZs=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchpersignal v0.121.0 h1:dnRf9xyjTBGHui949SS0A87fNa49Al32dog9XmeGY7I=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchpersignal v0.121.0/go.mod h1:ZAFQ1Te56fodNun+lmC/jFjVxhecumV882ct8sbdeAM=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.124.1 h1:E1e96GTHmiAfIfeYfA5ZVnOxud3+vbisGp0gE1tfd4s=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.124.1/go.mod h1:MOhFATtYSLad9nKunjh6uGf8nQUcWje2LPlhD2uu3do=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.124.1 h1:QX88VjAiUaG7SQe4A14b3JPgvzZK6OXpWoBd32Zlp1o=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.124.1/go.mod h1:jRsmTRbn1HkrOpoeXS0KGfXobrh7E3W/p9fLwLdpcB8=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/kafka/topic v0.121.0 h1:7xzxbwIBpb8LlMNY+RIdEnWTJH+pDu0LiL+tcIziEkA=
@@ -1296,6 +1302,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.121.0 h1
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.121.0/go.mod h1:PQjfR4WB9Q1MR9X/6IqxWGpKfavp9OF1fUcAqnPGGpQ=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure v0.124.1 h1:kPPjaOlncaJjoaJRUzW3TbqHM4v/+TtxC2Bzpa7z9X4=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure v0.124.1/go.mod h1:+6zqsZ1YGNsl7RUxfjzg7MKEWd5J6QAlq7zM3Nqy8UI=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.124.1 h1:9c6L4xlAMqhOg5y54Bc2B5t0i49yz7v2I6I8RY4Z0/o=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.124.1/go.mod h1:6f0N58o0cOHC0ApSM/qrooVmQza1eQ7L53PDE91uO1Q=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/opencensus v0.124.1 h1:IYxc8uPVCtKlgxjbYFGZ5/wXLdwAH1WQPKRlzaXtr8E=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/opencensus v0.124.1/go.mod h1:jFRiSEn2ss3e4CWG6GTDOJMYRI0C/N2XbMMIBzZuqZY=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.121.0 h1:QzV771eoAx2W9HCFq/YJVMNgEuG4lvalPTQIutheFhc=
@@ -1535,6 +1543,7 @@ go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA=
go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
go.opentelemetry.io/collector v0.124.0 h1:g/dfdGFhBcQI0ggGxTmGlJnJ6Yl6T2gVxQoIj4UfXCc=
go.opentelemetry.io/collector v0.124.0/go.mod h1:QzERYfmHUedawjr8Ph/CBEEkVqWS8IlxRLAZt+KHlCg=
go.opentelemetry.io/collector/client v1.30.0 h1:QbvOrvwUGcnVjnIBn2zyLLubisOjgh7kMgkzDAiYpHg=
go.opentelemetry.io/collector/client v1.30.0/go.mod h1:msXhZlNdAra2fZiyeT0o/xj43Kl1yvF9zYW0r+FhGUI=
@@ -1636,6 +1645,8 @@ go.opentelemetry.io/collector/internal/telemetry v0.124.0 h1:kzd1/ZYhLj4bt2pDB52
go.opentelemetry.io/collector/internal/telemetry v0.124.0/go.mod h1:ZjXjqV0dJ+6D4XGhTOxg/WHjnhdmXsmwmUSgALea66Y=
go.opentelemetry.io/collector/otelcol v0.124.0 h1:q/+ebTZgEZX+yFbvO7FeqpEtvtRPJ+YzZzHsVzqA71s=
go.opentelemetry.io/collector/otelcol v0.124.0/go.mod h1:mFGJZn5YuffdMVO/lPBavbW+R64Dgd3jOMgw2WAmJEM=
go.opentelemetry.io/collector/pdata/pprofile v0.124.0 h1:ZjL9wKqzP4BHj0/F1jfGxs1Va8B7xmYayipZeNVoWJE=
go.opentelemetry.io/collector/pdata/pprofile v0.124.0/go.mod h1:1EN3Gw5LSI4fSVma/Yfv/6nqeuYgRTm1/kmG5nE5Oyo=
go.opentelemetry.io/collector/pdata/testdata v0.124.0 h1:vY+pWG7CQfzzGSB5+zGYHQOltRQr59Ek9QiPe+rI+NY=
go.opentelemetry.io/collector/pdata/testdata v0.124.0/go.mod h1:lNH48lGhGv4CYk27fJecpsR1zYHmZjKgNrAprwjym0o=
go.opentelemetry.io/collector/pipeline v0.124.0 h1:hKvhDyH2GPnNO8LGL34ugf36sY7EOXPjBvlrvBhsOdw=
@@ -1668,6 +1679,8 @@ go.opentelemetry.io/collector/receiver/receivertest v0.124.0 h1:mx0290aXAo+wfjm4
go.opentelemetry.io/collector/receiver/receivertest v0.124.0/go.mod h1:3RpopRmIzx5T4zTStHJC0HHfd8YFWm8e9bia1HiuDtY=
go.opentelemetry.io/collector/receiver/xreceiver v0.124.0 h1:YigTUKk8p/aIfqaT0ST7teT9KbLThWD5n2km83byftw=
go.opentelemetry.io/collector/receiver/xreceiver v0.124.0/go.mod h1:NkTpmpAEDT17Dko4gpHUnRztrSkdSd6B0+Y4gfuCWIA=
go.opentelemetry.io/collector/semconv v0.124.0 h1:YTdo3UFwNyDQCh9DiSm2rbzAgBuwn/9dNZ0rv454goA=
go.opentelemetry.io/collector/semconv v0.124.0/go.mod h1:te6VQ4zZJO5Lp8dM2XIhDxDiL45mwX0YAQQWRQ0Qr9U=
go.opentelemetry.io/collector/service v0.124.0 h1:lUpizko/Y2P+XXbZ9wiKM8acLSt6ZIvC3/6/j6rcq4w=
go.opentelemetry.io/collector/service v0.124.0/go.mod h1:w2eL3KKOMW4CvqCWyZ3P/Qh1ZBEPGG/uRz/0LpHbpv0=
go.opentelemetry.io/collector/service/hostcapabilities v0.124.0 h1:ArxbARF7+bnzK8xLnN2G41KInbcN1aGhSBR76VeUQi8=
-2
View File
@@ -1,5 +1,3 @@
include ../.bingo/Variables.mk
.PHONY: all
all: dashboards
+2 -2
View File
@@ -89,7 +89,7 @@
"@crowdin/crowdin-api-client": "^1.42.0",
"@cypress/webpack-preprocessor": "6.0.4",
"@emotion/eslint-plugin": "11.12.0",
"@grafana/eslint-config": "8.1.0",
"@grafana/eslint-config": "8.2.0",
"@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules",
"@grafana/plugin-e2e": "2.1.7",
"@grafana/test-utils": "workspace:*",
@@ -195,7 +195,7 @@
"eslint-plugin-lodash": "8.0.0",
"eslint-plugin-no-barrel-files": "^1.1.1",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-testing-library": "^7.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"eslint-scope": "^8.1.0",
@@ -197,10 +197,6 @@ export interface FeatureToggles {
*/
provisioning?: boolean;
/**
* Experimental feature to use the secrets service for provisioning instead of the legacy secrets
*/
provisioningSecretsService?: boolean;
/**
* Start an additional https handler and write kubectl options
*/
grafanaAPIServerEnsureKubectlAccess?: boolean;
+5
View File
@@ -137,6 +137,8 @@ export interface LogRowContextOptions {
direction?: LogRowContextQueryDirection;
limit?: number;
scopedVars?: ScopedVars;
// Optional. Size of the time window to get logs before of after the referenced entry.
timeWindowMs?: number;
}
export enum LogRowContextQueryDirection {
@@ -181,6 +183,9 @@ export interface DataSourceWithLogsContextSupport<TQuery extends DataQuery = Dat
origQuery?: TQuery,
scopedVars?: ScopedVars
): React.ReactNode;
// Does the datasource support the user adjusting the time range in the logs context window? https://github.com/grafana/grafana/pull/109901
supportsAdjustableWindow?: boolean;
}
export const hasLogsContextSupport = (datasource: unknown): datasource is DataSourceWithLogsContextSupport => {
@@ -13,6 +13,9 @@ interface FolderPickerProps {
/* Folder UIDs to exclude from the picker, to prevent invalid operations */
excludeUIDs?: string[];
/* Start tree from this folder instead of root */
rootFolderUID?: string;
/* Show folders matching this permission, mainly used to also show folders user can view. Defaults to showing only folders user has Edit */
permission?: 'view' | 'edit';
@@ -111,6 +111,26 @@ export enum ResourceDimensionMode {
Mapping = 'mapping',
}
/**
* Links to a resource (image/svg path)
*/
export interface ResourceDimensionConfig extends BaseDimensionConfig {
fixed?: string;
mode: ResourceDimensionMode;
}
export enum ConnectionDirection {
Both = 'both',
Forward = 'forward',
None = 'none',
Reverse = 'reverse',
}
export enum DirectionDimensionMode {
Field = 'field',
Fixed = 'fixed',
}
export interface MapLayerOptions {
/**
* Custom options depending on the type
@@ -899,12 +919,9 @@ export interface DataSourceRef {
uid?: string;
}
/**
* Links to a resource (image/svg path)
*/
export interface ResourceDimensionConfig extends BaseDimensionConfig {
fixed?: string;
mode: ResourceDimensionMode;
export interface DirectionDimensionConfig extends BaseDimensionConfig {
fixed?: ConnectionDirection;
mode: DirectionDimensionMode;
}
export interface FrameGeometrySource {
@@ -35,7 +35,7 @@ TextDimensionMode: "fixed" | "field" | "template" @cuetsy(kind="enum")
TextDimensionConfig: {
BaseDimensionConfig
mode: TextDimensionMode
fixed?: string
fixed?: string
}@cuetsy(kind="interface")
ResourceDimensionMode: "fixed" | "field" | "mapping" @cuetsy(kind="enum")
@@ -44,6 +44,15 @@ ResourceDimensionMode: "fixed" | "field" | "mapping" @cuetsy(kind="enum")
ResourceDimensionConfig: {
BaseDimensionConfig
mode: ResourceDimensionMode
fixed?: string
fixed?: string
}@cuetsy(kind="interface")
ConnectionDirection: "forward" | "reverse" | "both" | "none" @cuetsy(kind="enum", memberNames="Forward|Reverse|Both|None")
DirectionDimensionMode: "fixed" | "field" @cuetsy(kind="enum")
DirectionDimensionConfig: {
BaseDimensionConfig
mode: DirectionDimensionMode
fixed?: ConnectionDirection
}@cuetsy(kind="interface")
@@ -80,6 +80,7 @@ export enum ConnectionPath {
export interface CanvasConnection {
color?: ui.ColorDimensionConfig;
direction?: ui.DirectionDimensionConfig;
path: ConnectionPath;
size?: ui.ScaleDimensionConfig;
source: ConnectionCoordinates;
@@ -1,9 +1,22 @@
import { HttpHandler } from 'msw';
import folderHandlers from './api/folders/handlers';
import searchHandlers from './api/search/handlers';
import teamsHandlers from './api/teams/handlers';
import appPlatformFolderHandlers from './apis/dashboard.grafana.app/v0alpha1/handlers';
import appPlatformDashboardv0alpha1Handlers from './apis/dashboard.grafana.app/v0alpha1/handlers';
import appPlatformFolderv1beta1Handlers from './apis/folder.grafana.app/v1beta1/handlers';
import appPlatformIamv0alpha1Handlers from './apis/iam.grafana.app/v0alpha1/handlers';
const allHandlers: HttpHandler[] = [...teamsHandlers, ...folderHandlers, ...appPlatformFolderHandlers];
const allHandlers: HttpHandler[] = [
// Legacy handlers
...teamsHandlers,
...folderHandlers,
...searchHandlers,
// App platform handlers
...appPlatformDashboardv0alpha1Handlers,
...appPlatformFolderv1beta1Handlers,
...appPlatformIamv0alpha1Handlers,
];
export default allHandlers;
@@ -1,3 +1,4 @@
import { Chance } from 'chance';
import { HttpResponse, http } from 'msw';
import { treeViewersCanEdit, wellFormedTree } from '../../../fixtures/folders';
@@ -6,6 +7,26 @@ const [mockTree] = wellFormedTree();
const [mockTreeThatViewersCanEdit] = treeViewersCanEdit();
const collator = new Intl.Collator();
// TODO: Generalise access control response and additional properties
const mockAccessControl = {
'dashboards.permissions:write': true,
'dashboards:create': true,
};
const additionalProperties = {
canAdmin: true,
canDelete: true,
canEdit: true,
canSave: true,
created: '2025-07-14T12:07:36+02:00',
createdBy: 'Anonymous',
hasAcl: false,
orgId: 1,
updated: '2025-07-15T18:01:36+02:00',
updatedBy: 'Anonymous',
url: '/grafana/dashboards/f/1ca93012-1ffc-5d64-ae2e-54835c234c67/rik-cujahda-pi',
version: 1,
};
const listFoldersHandler = () =>
http.get('/api/folders', ({ request }) => {
const url = new URL(request.url);
@@ -21,7 +42,9 @@ const listFoldersHandler = () =>
const folders = tree
.filter((v) => v.item.kind === 'folder' && v.item.parentUID === parentUid)
.map((folder) => {
const random = Chance(folder.item.uid);
return {
id: random.integer({ min: 1, max: 1000 }),
uid: folder.item.uid,
title: folder.item.kind === 'folder' ? folder.item.title : "invalid - this shouldn't happen",
};
@@ -33,17 +56,25 @@ const listFoldersHandler = () =>
});
const getFolderHandler = () =>
http.get('/api/folders/:uid', ({ params }) => {
http.get('/api/folders/:uid', ({ params, request }) => {
const { uid } = params;
const url = new URL(request.url);
const accessControlQueryParam = url.searchParams.get('accesscontrol');
const folder = mockTree.find((v) => v.item.uid === uid);
if (!folder) {
return HttpResponse.json({ message: 'folder not found', status: 'not-found' }, { status: 404 });
}
const random = Chance(folder.item.uid);
return HttpResponse.json({
id: random.integer({ min: 1, max: 1000 }),
title: folder?.item.title,
uid: folder?.item.uid,
...additionalProperties,
...(accessControlQueryParam ? { accessControl: mockAccessControl } : {}),
});
});
@@ -0,0 +1,73 @@
import { Chance } from 'chance';
import { HttpResponse, http } from 'msw';
import { wellFormedTree } from '../../../fixtures/folders';
const [mockTree] = wellFormedTree();
type FilterArray = Array<(v: (typeof mockTree)[number]) => boolean>;
const slugify = (str: string) => {
return str
.toLowerCase()
.replace(/[^\w ]+/g, '')
.replace(/ +/g, '-');
};
const getLegacySearchHandler = () =>
http.get('/api/search', ({ request }) => {
const folderFilter = new URL(request.url).searchParams.get('folderUIDs') || null;
const typeFilter = new URL(request.url).searchParams.get('type') || null;
// Workaround for the fixture kind being 'dashboard' instead of 'dash-db'
const mappedTypeFilter = typeFilter === 'dash-db' ? 'dashboard' : typeFilter;
const response = mockTree
.filter((filterItem) => {
const filters: FilterArray = [];
if (folderFilter && folderFilter !== 'general') {
filters.push(
({ item }) => (item.kind === 'folder' || item.kind === 'dashboard') && item.parentUID === folderFilter
);
}
if (folderFilter === 'general') {
filters.push(
({ item }) => (item.kind === 'folder' || item.kind === 'dashboard') && item.parentUID === undefined
);
}
if (mappedTypeFilter) {
filters.push(({ item }) => item.kind === mappedTypeFilter);
}
return filters.every((filterPredicate) => filterPredicate(filterItem));
})
.map(({ item }) => {
const random = Chance(item.uid);
const slugified = slugify(item.title || '');
const parentFolder =
item.kind === 'dashboard'
? mockTree.find((t) => t.item.kind === 'folder' && t.item.uid === item.parentUID)
: undefined;
return {
id: random.integer({ min: 1, max: 1000 }),
uid: item.uid,
orgId: 1,
title: item.title,
uri: `db/${slugified}`,
url: `/d/${item.uid}/${slugified}`,
folderUid: parentFolder?.item.uid,
folderTitle: parentFolder?.item.title,
slug: '',
type: item.kind,
tags: [],
isStarred: false,
sortMeta: 0,
isDeleted: false,
};
});
return HttpResponse.json(response);
});
export default [getLegacySearchHandler()];
@@ -0,0 +1,117 @@
import { HttpResponse, http } from 'msw';
import { wellFormedTree } from '../../../../fixtures/folders';
const [mockTree] = wellFormedTree();
const getFolderHandler = () =>
http.get<{ folderUid: string; namespace: string }>(
'/apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:folderUid',
({ params }) => {
const { folderUid, namespace } = params;
const response = mockTree.find(({ item }) => {
return item.uid === folderUid;
});
if (!response) {
return HttpResponse.json(
{
kind: 'Status',
apiVersion: 'v1',
metadata: {},
status: 'Failure',
message: 'folder not found',
code: 404,
},
{ status: 404 }
);
}
return HttpResponse.json({
kind: 'Folder',
apiVersion: 'folder.grafana.app/v1beta1',
metadata: {
name: response.item.uid,
namespace,
uid: response.item.uid,
creationTimestamp: '2023-01-01T00:00:00Z',
annotations: {
// TODO: Generalise annotations in fixture data
'grafana.app/createdBy': 'user:1',
'grafana.app/updatedBy': 'user:2',
'grafana.app/managedBy': 'user',
'grafana.app/updatedTimestamp': '2024-01-01T00:00:00Z',
'grafana.app/folder': response.item.kind === 'folder' ? response.item.parentUID : undefined,
},
labels: {
'grafana.app/deprecatedInternalID': '123',
},
},
spec: { title: response.item.title, description: '' },
status: {},
});
}
);
const getFolderParentsHandler = () =>
http.get<{ folderUid: string; namespace: string }>(
'/apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:folderUid/parents',
({ params }) => {
const { folderUid } = params;
const folder = mockTree.find(({ item }) => {
return item.kind === 'folder' && item.uid === folderUid;
});
if (!folder || folder.item.kind !== 'folder') {
return HttpResponse.json({
kind: 'Status',
apiVersion: 'v1',
metadata: {},
status: 'Failure',
message: 'folder not found',
code: 404,
});
}
const findParents = (parents: Array<(typeof mockTree)[number]>, folderUid?: string) => {
if (!folderUid) {
return parents;
}
const parent = mockTree.find(({ item }) => {
return item.kind === 'folder' && item.uid === folderUid;
});
if (parent) {
parents.push(parent);
return findParents(parents, parent.item.kind === 'folder' ? parent.item.parentUID : undefined);
}
return parents;
};
const parents = findParents([], folder?.item?.parentUID);
const mapped = parents.map((parent) => ({
name: parent.item.uid,
title: parent.item.title,
parentUid: parent.item.kind === 'folder' ? parent.item.parentUID : undefined,
}));
if (folder) {
mapped.push({
name: folder.item.uid,
title: folder.item.title,
parentUid: folder.item.parentUID,
});
}
return HttpResponse.json({
kind: 'FolderInfoList',
apiVersion: 'folder.grafana.app/v1beta1',
metadata: {},
items: mapped,
});
}
);
export default [getFolderHandler(), getFolderParentsHandler()];
@@ -0,0 +1,29 @@
import { HttpResponse, http } from 'msw';
const getDisplayMapping = () =>
http.get<{ namespace: string }>('/apis/iam.grafana.app/v0alpha1/namespaces/:namespace/display', ({ request }) => {
const url = new URL(request.url);
const keys = url.searchParams.getAll('key');
// Turn query params such as `user:1` into mock mapping of `User 1` etc.
const mockMappings = keys.map((key) => {
const [_, id] = key.split(':');
const displayName = `User ${id}`;
return {
identity: {
type: 'user',
name: `u00000000${id}`,
},
displayName,
internalId: parseInt(id, 10),
};
});
return HttpResponse.json({
metadata: {},
keys,
display: mockMappings,
});
});
export default [getDisplayMapping()];
@@ -37,7 +37,7 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
pickerTriggerRef = createRef<any>();
render() {
const { theme, children, onChange, color, id } = this.props;
const { theme, children, onChange, color } = this.props;
const styles = getStyles(theme);
const popoverElement = React.createElement(popover, {
...{ ...this.props, children: null },
@@ -67,7 +67,6 @@ export const colorPickerFactory = <T extends ColorPickerProps>(
})
) : (
<ColorSwatch
id={id}
ref={this.pickerTriggerRef}
onClick={showPopper}
onMouseLeave={hidePopper}
@@ -22,7 +22,6 @@ export interface ColorPickerProps extends Themeable2 {
color: string;
onChange: ColorPickerChangeHandler;
enableNamedColors?: boolean;
id?: string;
}
export interface Props<T> extends ColorPickerProps, PopoverContentProps {
@@ -66,6 +66,31 @@ export const Basic: Story = {
},
};
export const WithInfoOption: Story = {
name: 'With infoOption',
args: {
...commonArgs,
options: [
...commonArgs.options,
{ label: 'Can’t find your country? Select “Other” or contact an admin', value: '__INFO__', infoOption: true },
],
},
render: (args) => {
const [{ value }, setArgs] = useArgs();
return (
<MultiCombobox
{...args}
value={value}
onChange={(val) => {
onChangeAction(val);
setArgs({ value: val });
}}
/>
);
},
};
export const AutoSize: Story = {
args: { ...commonArgs, width: 'auto', minWidth: 20 },
render: (args) => {
@@ -10,7 +10,7 @@ import { useFieldDisplayNames, useSelectOptions, frameHasName } from './utils';
type Props = StandardEditorProps<string, FieldNamePickerConfigSettings>;
// Pick a field name out of the fields
export const FieldNamePicker = ({ value, onChange, context, item, id }: Props) => {
export const FieldNamePicker = ({ value, onChange, context, item }: Props) => {
const settings: FieldNamePickerConfigSettings = item.settings ?? {};
const names = useFieldDisplayNames(context.data, settings?.filter);
const selectOptions = useSelectOptions(names, value, undefined, undefined, settings.baseNameMode);
@@ -29,7 +29,6 @@ export const FieldNamePicker = ({ value, onChange, context, item, id }: Props) =
return (
<>
<Select
inputId={id}
value={selectedOption}
placeholder={
settings.placeholderText ?? t('grafana-ui.matchers-ui.field-name-picker.placeholder', 'Select field')
@@ -41,11 +41,10 @@ export interface Props {
data: DataFrame[];
onChange: (value: string) => void;
placeholder?: string;
id?: string;
}
// Not exported globally... but used in grafana core
export function RefIDPicker({ value, data, onChange, placeholder, id }: Props) {
export function RefIDPicker({ value, data, onChange, placeholder }: Props) {
const listOfRefIds = useMemo(() => getListOfQueryRefIds(data), [data]);
const [priorSelectionState, updatePriorSelectionState] = useState<{
@@ -78,7 +77,6 @@ export function RefIDPicker({ value, data, onChange, placeholder, id }: Props) {
}
return (
<Select
inputId={id}
options={listOfRefIds}
onChange={onFilterChange}
isClearable={true}
@@ -116,10 +114,9 @@ export interface MultiProps {
data: DataFrame[];
onChange: (value: string[]) => void;
placeholder?: string;
id?: string;
}
export function RefIDMultiPicker({ value, data, onChange, placeholder, id }: MultiProps) {
export function RefIDMultiPicker({ value, data, onChange, placeholder }: MultiProps) {
const listOfRefIds = useMemo(() => getListOfQueryRefIds(data), [data]);
const [priorSelectionState, updatePriorSelectionState] = useState<{
@@ -175,7 +172,6 @@ export function RefIDMultiPicker({ value, data, onChange, placeholder, id }: Mul
}
return (
<MultiSelect
inputId={id}
options={listOfRefIds}
onChange={onFilterChange}
isClearable={true}
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { formattedValueToString } from '@grafana/data';
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
import { MaybeWrapWithLink } from '../components/MaybeWrapWithLink';
import { AutoCellProps, TableCellStyles } from '../types';
export function AutoCell({ value, field, rowIdx }: AutoCellProps) {
@@ -2,7 +2,7 @@ import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMin
import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@grafana/schema';
import { BarGauge } from '../../../BarGauge/BarGauge';
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
import { MaybeWrapWithLink } from '../components/MaybeWrapWithLink';
import { TABLE } from '../constants';
import { BarGaugeCellProps } from '../types';
import { getCellOptions, getAlignmentFactor } from '../utils';
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import { TableCellDisplayMode } from '../../types';
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
import { MaybeWrapWithLink } from '../components/MaybeWrapWithLink';
import { ImageCellProps, TableCellStyles } from '../types';
export const ImageCell = ({ cellOptions, field, value, rowIdx }: ImageCellProps) => {
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { renderMarkdown } from '@grafana/data';
import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
import { MaybeWrapWithLink } from '../components/MaybeWrapWithLink';
import { MarkdownCellProps, TableCellStyles } from '../types';
export function MarkdownCell({ field, rowIdx, disableSanitizeHtml }: MarkdownCellProps) {
@@ -9,6 +9,7 @@ import {
DataGridHandle,
DataGridProps,
RenderCellProps,
Renderers,
RenderRowProps,
Row,
SortColumn,
@@ -42,6 +43,7 @@ import { TableCellTooltip } from './components/TableCellTooltip';
import { COLUMN, TABLE } from './constants';
import {
useColumnResize,
useColWidths,
useFilteredRows,
useFooterCalcs,
useHeaderHeight,
@@ -51,6 +53,7 @@ import {
useSortedRows,
} from './hooks';
import {
getCellActionStyles,
getDefaultCellStyles,
getFooterStyles,
getGridStyles,
@@ -58,11 +61,19 @@ import {
getLinkStyles,
getTooltipStyles,
} from './styles';
import { TableNGProps, TableRow, TableSummaryRow, TableColumn, InspectCellProps, TableCellStyleOptions } from './types';
import {
TableNGProps,
TableRow,
TableSummaryRow,
TableColumn,
InspectCellProps,
TableCellStyleOptions,
FromFieldsResult,
CellRootRenderer,
} from './types';
import {
applySort,
canFieldBeColorized,
computeColWidths,
createTypographyContext,
displayJsonValue,
extractPixelValue,
@@ -84,7 +95,7 @@ import {
withDataLinksActionsTooltip,
} from './utils';
type CellRootRenderer = (key: React.Key, props: CellRendererProps<TableRow, TableSummaryRow>) => React.ReactNode;
const EXPANDED_COLUMN_KEY = 'expanded';
export function TableNG(props: TableNGProps) {
const {
@@ -148,6 +159,7 @@ export function TableNG(props: TableNGProps) {
} = useSortedRows(filteredRows, data.fields, { hasNestedFrames, initialSortBy });
const [inspectCell, setInspectCell] = useState<InspectCellProps | null>(null);
const [tooltipState, setTooltipState] = useState<DataLinksActionsTooltipState>();
const [expandedRows, setExpandedRows] = useState(() => new Set<number>());
// vt scrollbar accounting for column auto-sizing
@@ -171,20 +183,9 @@ export function TableNG(props: TableNGProps) {
),
[theme]
);
const widths = useMemo(() => computeColWidths(visibleFields, availableWidth), [visibleFields, availableWidth]);
const numColsFullyInView = useMemo(
() =>
widths.reduce(
([count, remainingWidth], nextWidth) => {
if (remainingWidth - nextWidth >= 0) {
return [count + 1, remainingWidth - nextWidth];
}
return [count, 0];
},
[0, availableWidth]
)[0],
[widths, availableWidth]
);
const [widths, numFrozenColsFullyInView] = useColWidths(visibleFields, availableWidth, frozenColumns);
const headerHeight = useHeaderHeight({
columnWidths: widths,
fields: visibleFields,
@@ -270,15 +271,84 @@ export function TableNG(props: TableNGProps) {
[enableVirtualization, resizeHandler, sortColumns, rowHeight, hasFooter, setSortColumns, onSortByChange]
);
interface Schema {
columns: TableColumn[];
cellRootRenderers: Record<string, CellRootRenderer>;
colsWithTooltip: Record<string, boolean>;
}
const buildNestedTableExpanderColumn = useCallback(
(
nestedColumns: TableColumn[],
hasNestedHeaders: boolean,
renderers: Renderers<TableRow, TableSummaryRow>
): TableColumn => ({
key: EXPANDED_COLUMN_KEY,
name: '',
field: {
name: '',
type: FieldType.other,
config: {},
values: [],
},
cellClass(row) {
if (row.__depth !== 0) {
return styles.cellNested;
}
return;
},
colSpan(args) {
return args.type === 'ROW' && args.row.__depth === 1 ? data.fields.length : 1;
},
renderCell: ({ row }) => {
if (row.__depth === 0) {
const rowIdx = row.__index;
const { columns, cellRootRenderers, colsWithTooltip } = useMemo(() => {
const fromFields = (f: Field[], widths: number[]) => {
const result: Schema = {
return (
<RowExpander
isExpanded={expandedRows.has(rowIdx)}
onCellExpand={() => {
if (expandedRows.has(rowIdx)) {
expandedRows.delete(rowIdx);
} else {
expandedRows.add(rowIdx);
}
setExpandedRows(new Set(expandedRows));
}}
/>
);
}
// Type guard to check if data exists as it's optional
const nestedData = row.data;
if (!nestedData) {
return null;
}
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
if (!expandedRecords.length) {
return (
<div className={styles.noDataNested}>
<Trans i18nKey="grafana-ui.table.nested-table.no-data">No data</Trans>
</div>
);
}
return (
<DataGrid<TableRow, TableSummaryRow>
{...commonDataGridProps}
className={clsx(styles.grid, styles.gridNested)}
headerRowClass={clsx(styles.headerRow, { [styles.displayNone]: !hasNestedHeaders })}
headerRowHeight={hasNestedHeaders ? TABLE.HEADER_HEIGHT : 0}
columns={nestedColumns}
rows={expandedRecords}
renderers={renderers}
/>
);
},
width: COLUMN.EXPANDER_WIDTH,
minWidth: COLUMN.EXPANDER_WIDTH,
}),
[commonDataGridProps, data.fields.length, expandedRows, sortColumns, styles]
);
const fromFields = useCallback(
(f: Field[], widths: number[]): FromFieldsResult => {
const result: FromFieldsResult = {
columns: [],
cellRootRenderers: {},
colsWithTooltip: {},
@@ -334,11 +404,7 @@ export function TableNG(props: TableNGProps) {
// helps us avoid string cx and emotion per-cell
const cellActionClassName = showActions
? clsx(
'table-cell-actions',
styles.cellActions,
justifyContent === 'flex-end' ? styles.cellActionsEnd : styles.cellActionsStart
)
? clsx('table-cell-actions', getCellActionStyles(theme, textAlign))
: undefined;
const shouldOverflow = rowHeight !== 'auto' && shouldTextOverflow(field);
@@ -509,13 +575,13 @@ export function TableNG(props: TableNGProps) {
}
}
const column: TableColumn = {
result.columns.push({
field,
key: displayName,
name: displayName,
width,
headerCellClass,
frozen: Math.min(frozenColumns, numColsFullyInView) > i,
frozen: Math.min(frozenColumns, numFrozenColsFullyInView) > i,
renderCell: renderCellContent,
renderHeaderCell: ({ column, sortDirection }) => (
<HeaderCell
@@ -543,136 +609,78 @@ export function TableNG(props: TableNGProps) {
}
return <div className={footerStyles.footerCell}>{footerCalcs[i]}</div>;
},
};
result.columns.push(column);
});
});
return result;
};
},
[
applyToRowBgFn,
crossFilterOrder,
crossFilterRows,
data,
disableSanitizeHtml,
filter,
footerCalcs,
frozenColumns,
getCellActions,
isCountRowsSet,
numFrozenColsFullyInView,
onCellFilterAdded,
rowHeight,
rowHeightFn,
rows,
setFilter,
showTypeIcons,
theme,
timeRange,
]
);
// set up the first row's nested data and the nest field widths using useColWidths to avoid
// unnecessary re-renders on re-size.
const firstRowNestedData = useMemo(
() => (hasNestedFrames ? rows.find((r) => r.data)?.data : undefined),
[hasNestedFrames, rows]
);
const [nestedFieldWidths] = useColWidths(firstRowNestedData?.fields ?? [], availableWidth);
const { columns, cellRootRenderers, colsWithTooltip } = useMemo(() => {
const result = fromFields(visibleFields, widths);
// handle nested frames rendering from here.
if (!hasNestedFrames) {
// if nested frames are present, augment the columns to include the nested table expander column.
if (!firstRowNestedData) {
return result;
}
// pre-calculate renderRow and expandedColumns based on the first nested frame's fields.
const firstNestedData = rows.find((r) => r.data)?.data;
if (!firstNestedData) {
return result;
}
const hasNestedHeaders = firstNestedData.meta?.custom?.noHeader !== true;
const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
const hasNestedHeaders = firstRowNestedData.meta?.custom?.noHeader !== true;
const renderRow = renderRowFactory(firstRowNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields(
firstNestedData.fields,
computeColWidths(firstNestedData.fields, availableWidth)
firstRowNestedData.fields,
nestedFieldWidths
);
const renderCellRoot: CellRootRenderer = (key, props) => nestedCellRootRenderers[props.column.key](key, props);
result.cellRootRenderers.expanded = (key, props) => <Cell key={key} {...props} />;
const expanderCellRenderer: CellRootRenderer = (key, props) => <Cell key={key} {...props} />;
result.cellRootRenderers[EXPANDED_COLUMN_KEY] = expanderCellRenderer;
// If we have nested frames, we need to add a column for the row expansion
result.columns.unshift({
key: 'expanded',
name: '',
field: {
name: '',
type: FieldType.other,
config: {},
values: [],
},
cellClass(row) {
if (row.__depth !== 0) {
return styles.cellNested;
}
return;
},
colSpan(args) {
return args.type === 'ROW' && args.row.__depth === 1 ? data.fields.length : 1;
},
renderCell: ({ row }) => {
if (row.__depth === 0) {
const rowIdx = row.__index;
return (
<RowExpander
isExpanded={expandedRows.has(rowIdx)}
onCellExpand={() => {
if (expandedRows.has(rowIdx)) {
expandedRows.delete(rowIdx);
} else {
expandedRows.add(rowIdx);
}
setExpandedRows(new Set(expandedRows));
}}
/>
);
}
// Type guard to check if data exists as it's optional
const nestedData = row.data;
if (!nestedData) {
return null;
}
const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns);
if (!expandedRecords.length) {
return (
<div className={styles.noDataNested}>
<Trans i18nKey="grafana-ui.table.nested-table.no-data">No data</Trans>
</div>
);
}
return (
<DataGrid<TableRow, TableSummaryRow>
{...commonDataGridProps}
className={clsx(styles.grid, styles.gridNested)}
headerRowClass={clsx(styles.headerRow, { [styles.displayNone]: !hasNestedHeaders })}
headerRowHeight={hasNestedHeaders ? TABLE.HEADER_HEIGHT : 0}
columns={nestedColumns}
rows={expandedRecords}
renderers={{ renderRow, renderCell: renderCellRoot }}
/>
);
},
width: COLUMN.EXPANDER_WIDTH,
minWidth: COLUMN.EXPANDER_WIDTH,
});
result.columns.unshift(
buildNestedTableExpanderColumn(nestedColumns, hasNestedHeaders, {
renderRow,
renderCell: (key, props) => nestedCellRootRenderers[props.column.key](key, props),
})
);
return result;
}, [
applyToRowBgFn,
availableWidth,
commonDataGridProps,
crossFilterOrder,
crossFilterRows,
data,
disableSanitizeHtml,
buildNestedTableExpanderColumn,
enableSharedCrosshair,
expandedRows,
filter,
footerCalcs,
frozenColumns,
getCellActions,
hasNestedFrames,
isCountRowsSet,
numColsFullyInView,
onCellFilterAdded,
firstRowNestedData,
fromFields,
nestedFieldWidths,
panelContext,
rowHeight,
rowHeightFn,
rows,
setFilter,
showTypeIcons,
sortColumns,
styles,
timeRange,
theme,
visibleFields,
widths,
]);
@@ -680,18 +688,16 @@ export function TableNG(props: TableNGProps) {
// invalidate columns on every structureRev change. this supports width editing in the fieldConfig.
// eslint-disable-next-line react-hooks/exhaustive-deps
const structureRevColumns = useMemo(() => columns, [columns, structureRev]);
const renderCellRoot: CellRootRenderer = useCallback(
(key, props) => cellRootRenderers[props.column.key](key, props),
[cellRootRenderers]
);
// we need to have variables with these exact names for the localization to work properly
const itemsRangeStart = pageRangeStart;
const displayedEnd = pageRangeEnd;
const numRows = sortedRows.length;
const renderCellRoot: CellRootRenderer = (key, props) => {
return cellRootRenderers[props.column.key](key, props);
};
const [tooltipState, setTooltipState] = useState<DataLinksActionsTooltipState>();
return (
<>
<DataGrid<TableRow, TableSummaryRow>
@@ -0,0 +1,199 @@
import { render, screen } from '@testing-library/react';
import { ActionType, Field, FieldType, HttpRequestMethod } from '@grafana/data';
import { MaybeWrapWithLink } from './MaybeWrapWithLink';
describe('MaybeWrapWithLink', () => {
describe('single link', () => {
it('renders children as a link when there is a single link', () => {
const link = { title: 'My link', url: 'http://example.com' };
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => [{ title: link.title, href: link.url, target: '_blank', origin: field }]),
config: {
links: [link],
actions: [],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const linkElement = screen.getByTitle(link.title);
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', link.url);
expect(linkElement).toHaveTextContent('Test Link');
});
it('does not throw if getLinks unexpectedly returns nothing when a single link is present', () => {
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => []),
config: {
links: [{ title: 'My link', url: 'http://example.com' }],
actions: [],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const childElement = screen.getByText('Test Link');
expect(childElement).toBeInTheDocument();
});
});
describe('multi link and/or actions', () => {
it('renders a popup target link if multiple links are present', () => {
const links = [
{ title: 'My link', url: 'http://example.com' },
{ title: 'Another link', url: 'http://example.com' },
];
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => links.map((l) => ({ title: l.title, href: l.url, target: '_blank', origin: field }))),
config: {
links,
actions: [],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const linkElement = screen.getByTitle('view data links and actions');
expect(linkElement).toBeInTheDocument();
expect(linkElement.tagName).toBe('A');
expect(linkElement).toHaveAttribute('aria-haspopup', 'menu');
expect(linkElement).toHaveTextContent('Test Link');
});
it('renders a popup target link if multiple actions are present', () => {
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => []),
config: {
links: [],
actions: [
{
type: ActionType.Fetch,
title: 'My action',
[ActionType.Fetch]: { method: HttpRequestMethod.GET, url: 'http://example.com' },
},
{
type: ActionType.Fetch,
title: 'Another action',
[ActionType.Fetch]: { method: HttpRequestMethod.POST, url: 'http://example.com' },
},
],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const linkElement = screen.getByTitle('view data links and actions');
expect(linkElement).toBeInTheDocument();
expect(linkElement.tagName).toBe('A');
expect(linkElement).toHaveAttribute('aria-haspopup', 'menu');
expect(linkElement).toHaveTextContent('Test Link');
});
it('renders a popup target link if a single action is present', () => {
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => []),
config: {
links: [],
actions: [
{
type: ActionType.Fetch,
title: 'My action',
[ActionType.Fetch]: { method: HttpRequestMethod.GET, url: 'http://example.com' },
},
],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const linkElement = screen.getByTitle('view data links and actions');
expect(linkElement).toBeInTheDocument();
expect(linkElement.tagName).toBe('A');
expect(linkElement).toHaveAttribute('aria-haspopup', 'menu');
expect(linkElement).toHaveTextContent('Test Link');
});
it('renders a popup target link if a mixture of actions and links are present', () => {
const links = [{ title: 'My link', url: 'http://example.com' }];
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => links.map((l) => ({ title: l.title, href: l.url, target: '_blank', origin: field }))),
config: {
links,
actions: [
{
type: ActionType.Fetch,
title: 'My action',
[ActionType.Fetch]: { method: HttpRequestMethod.GET, url: 'http://example.com' },
},
],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const linkElement = screen.getByTitle('view data links and actions');
expect(linkElement).toBeInTheDocument();
expect(linkElement.tagName).toBe('A');
expect(linkElement).toHaveAttribute('aria-haspopup', 'menu');
expect(linkElement).toHaveTextContent('Test Link');
});
});
describe('no links or actions', () => {
it('passes the children through when no links or actions are present', () => {
const links = [
{ title: 'My link', url: 'http://example.com' },
{ title: 'Another link', url: 'http://example.com' },
];
const field: Field = {
type: FieldType.string,
name: 'Test Field',
values: [],
getLinks: jest.fn(() => []),
config: {
links,
actions: [],
},
};
const rowIdx = 0;
const children = <span>Test Link</span>;
render(<MaybeWrapWithLink field={field} rowIdx={rowIdx} children={children} />);
const childElement = screen.getByText('Test Link');
expect(childElement).toBeInTheDocument();
});
});
});
@@ -1,10 +1,10 @@
import { memo, ReactNode } from 'react';
import { Field } from '@grafana/data';
import { t } from '@grafana/i18n';
import { renderSingleLink } from '../DataLinksActionsTooltip';
import { getCellLinks } from './utils';
import { renderSingleLink } from '../../DataLinksActionsTooltip';
import { getCellLinks } from '../utils';
interface MaybeWrapWithLinkProps {
field: Field;
@@ -23,8 +23,12 @@ export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWit
}
// as faux link that acts as hit-area for tooltip activation
else if (linksCount + actionsCount > 0) {
// eslint-disable-next-line jsx-a11y/anchor-is-valid
return <a aria-haspopup="menu">{children}</a>;
return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a title={t('table.link-wrapper.menu', 'view data links and actions')} aria-haspopup="menu">
{children}
</a>
);
}
// raw value
@@ -477,7 +477,7 @@ describe('TableNG hooks', () => {
}),
columnWidths: [100, 100, 100],
enabled: true,
typographyCtx: { ...typographyCtx, avgCharWidth: 5, measureHeight: jest.fn(() => 38) },
typographyCtx: { ...typographyCtx, avgCharWidth: 5, measureHeight: jest.fn(() => 44) },
sortColumns: [],
});
});
@@ -518,7 +518,7 @@ describe('TableNG hooks', () => {
});
});
expect(heightFn).toHaveBeenCalledWith('Longer name that needs wrapping', 86, modifiedFields[0], -1, 6);
expect(heightFn).toHaveBeenCalledWith('Longer name that needs wrapping', 86, modifiedFields[0], -1, 22);
modifiedFields = fields.map((field) => {
if (field.name === 'name') {
@@ -549,7 +549,21 @@ describe('TableNG hooks', () => {
});
});
expect(heightFn).toHaveBeenCalledWith('Longer name that needs wrapping', 26, modifiedFields[0], -1, 6);
expect(heightFn).toHaveBeenCalledWith('Longer name that needs wrapping', 26, modifiedFields[0], -1, 22);
});
it('does not throw if a field has been deleted but the colWidth has not yet been updated', () => {
const { fields } = setupData();
const { result } = renderHook(() => {
return useHeaderHeight({
fields,
columnWidths: [100, 100, 100, 100],
enabled: true,
typographyCtx,
sortColumns: [],
});
});
expect(result.current).toBe(28);
});
});
@@ -1,7 +1,14 @@
import { useState, useMemo, useCallback, useRef, useLayoutEffect, RefObject, CSSProperties } from 'react';
import { useState, useMemo, useCallback, useRef, useLayoutEffect, RefObject, CSSProperties, useEffect } from 'react';
import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-grid';
import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data';
import {
compareArrayValues,
Field,
fieldReducers,
FieldType,
formattedValueToString,
reduceField,
} from '@grafana/data';
import { TableColumnResizeActionCallback } from '../types';
@@ -13,6 +20,7 @@ import {
applySort,
getColumnTypes,
getRowHeight,
computeColWidths,
buildHeaderHeightMeasurers,
buildCellHeightMeasurers,
} from './utils';
@@ -183,16 +191,17 @@ export function usePaginatedRows(
return rows.slice(0, 100).reduce((avg, row, _, { length }) => avg + rowHeight(row) / length, 0);
}, [rows, rowHeight, enabled]);
const smallPagination = useMemo(() => enabled && width < TABLE.PAGINATION_LIMIT, [enabled, width]);
// using dimensions of the panel, calculate pagination parameters
const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd, smallPagination } = useMemo((): {
const { numPages, rowsPerPage, pageRangeStart, pageRangeEnd } = useMemo((): {
numPages: number;
rowsPerPage: number;
pageRangeStart: number;
pageRangeEnd: number;
smallPagination: boolean;
} => {
if (!enabled) {
return { numPages: 0, rowsPerPage: 0, pageRangeStart: 1, pageRangeEnd: numRows, smallPagination: false };
return { numPages: 0, rowsPerPage: 0, pageRangeStart: 1, pageRangeEnd: numRows };
}
// calculate number of rowsPerPage based on height stack
@@ -207,16 +216,15 @@ export function usePaginatedRows(
if (pageRangeEnd > numRows) {
pageRangeEnd = numRows;
}
const smallPagination = width < TABLE.PAGINATION_LIMIT;
const numPages = Math.ceil(numRows / rowsPerPage);
return {
numPages,
rowsPerPage,
pageRangeStart,
pageRangeEnd,
smallPagination,
};
}, [width, height, headerHeight, footerHeight, avgRowHeight, enabled, numRows, page]);
}, [height, headerHeight, footerHeight, avgRowHeight, enabled, numRows, page]);
// safeguard against page overflow on panel resize or other factors
useLayoutEffect(() => {
@@ -345,13 +353,19 @@ export function useHeaderHeight({
const columnAvailableWidths = useMemo(
() =>
columnWidths.map((c, idx) => {
if (idx >= fields.length) {
return 0; // no width available for this column yet
}
let width = c - 2 * TABLE.CELL_PADDING - TABLE.BORDER_RIGHT;
const field = fields[idx];
// filtering icon
if (fields[idx]?.config?.custom?.filterable) {
if (field.config?.custom?.filterable) {
width -= perIconSpace;
}
// sorting icon
if (sortColumns.some((col) => col.columnKey === getDisplayName(fields[idx]))) {
if (sortColumns.some((col) => col.columnKey === getDisplayName(field))) {
width -= perIconSpace;
}
// type icon
@@ -368,7 +382,15 @@ export function useHeaderHeight({
if (!enabled) {
return 0;
}
return getRowHeight(fields, -1, columnAvailableWidths, TABLE.HEADER_HEIGHT, measurers, TABLE.CELL_PADDING);
return getRowHeight(
fields,
-1,
columnAvailableWidths,
TABLE.HEADER_HEIGHT,
measurers,
TABLE.LINE_HEIGHT,
TABLE.CELL_PADDING
);
}, [fields, enabled, columnAvailableWidths, measurers]);
return headerHeight;
@@ -534,3 +556,45 @@ export function useScrollbarWidth(ref: RefObject<DataGridHandle>, height: number
return scrollbarWidth;
}
const numIsEqual = (a: number, b: number) => a === b;
export function useColWidths(
visibleFields: Field[],
availableWidth: number,
frozenColumns?: number
): [number[], number] {
const [widths, setWidths] = useState<number[]>(computeColWidths(visibleFields, availableWidth));
// only replace the widths array if something actually changed
useEffect(() => {
const newWidths = computeColWidths(visibleFields, availableWidth);
if (!compareArrayValues(widths, newWidths, numIsEqual)) {
setWidths(newWidths);
}
}, [availableWidth, widths, visibleFields]);
// this is to avoid buggy situations where all visible columns are frozen
const numFrozenColsFullyInView = useMemo(() => {
if (!frozenColumns || frozenColumns <= 0) {
return -1;
}
const fullyVisibleCols = widths.reduce(
([count, remainingWidth], nextWidth) => {
if (remainingWidth - nextWidth >= 0) {
return [count + 1, remainingWidth - nextWidth];
}
return [count, 0];
},
[0, availableWidth]
)[0];
// de-noise memoized changes to the columns array, and only change this
// number when the number of frozen columns changes or once there are fewer
// visible columns than the number of frozen columns.
return Math.min(fullyVisibleCols, frozenColumns);
}, [widths, availableWidth, frozenColumns]);
return [widths, numFrozenColsFullyInView];
}
@@ -88,19 +88,6 @@ export const getGridStyles = (theme: GrafanaTheme2, enablePagination?: boolean,
color: theme.colors.text.secondary,
fontSize: theme.typography.h4.fontSize,
}),
cellActions: css({
display: 'none',
position: 'absolute',
top: 0,
margin: 'auto',
height: '100%',
color: theme.colors.text.primary,
background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
padding: theme.spacing.x0_5,
paddingInlineStart: theme.spacing.x1,
}),
cellActionsEnd: css({ left: 0 }),
cellActionsStart: css({ right: 0 }),
headerRow: css({
paddingBlockStart: 0,
fontWeight: 'normal',
@@ -158,6 +145,20 @@ export const getDefaultCellStyles: TableCellStyles = (theme, { textAlign, should
},
});
export const getCellActionStyles = (theme: GrafanaTheme2, textAlign: TextAlign) =>
css({
display: 'none',
position: 'absolute',
top: 0,
margin: 'auto',
height: '100%',
color: theme.colors.text.primary,
background: theme.isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
padding: theme.spacing.x0_5,
paddingInlineStart: theme.spacing.x1,
[textAlign === 'right' ? 'left' : 'right']: 0,
});
export const getLinkStyles = (theme: GrafanaTheme2, canBeColorized: boolean) =>
css({
a: {
@@ -1,5 +1,5 @@
import { FC, SyntheticEvent } from 'react';
import { Column } from 'react-data-grid';
import { CellRendererProps, Column } from 'react-data-grid';
import {
DataFrame,
@@ -306,3 +306,11 @@ export interface MeasureCellHeightEntry {
*/
fieldIdxs: number[];
}
export type CellRootRenderer = (key: React.Key, props: CellRendererProps<TableRow, TableSummaryRow>) => React.ReactNode;
export interface FromFieldsResult {
columns: TableColumn[];
cellRootRenderers: Record<string, CellRootRenderer>;
colsWithTooltip: Record<string, boolean>;
}
@@ -9,7 +9,6 @@ export interface UnitPickerProps {
onChange: (item?: string) => void;
value?: string;
width?: number;
id?: string;
}
function formatCreateLabel(input: string) {
@@ -22,7 +21,7 @@ export class UnitPicker extends PureComponent<UnitPickerProps> {
};
render() {
const { value, width, id } = this.props;
const { value, width } = this.props;
// Set the current selection
let current: SelectableValue<string> | undefined = undefined;
@@ -57,7 +56,6 @@ export class UnitPicker extends PureComponent<UnitPickerProps> {
return (
<Cascader
id={id}
width={width}
initialValue={current && current.label}
allowCustomValue
+2 -3
View File
@@ -82,6 +82,5 @@ They can observe its output into the `public/api-merged.json` and `public/openap
Finally, they can browser and try out both the OpenAPI v2 and v3 via the Swagger UI editor (served by the grafana server) by navigating to `/swagger`.
If there are any issues generating the specifications (e.g., diff containing unrelated changes to your PR or unusually large diff), please run the following two commands to ensure your Swagger version is up to date, then re-run the make commands.
- `go install github.com/bwplotka/bingo@latest`
- `bingo get github.com/go-swagger/go-swagger/cmd/swagger@v0.30.2`
If there are any issues generating the specifications (e.g., diff containing unrelated changes to your PR or unusually large diff), please run the following command to ensure your Swagger version is up to date, then re-run the make commands.
- `go tool github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6`
-1
View File
@@ -199,7 +199,6 @@ func Wire(d *dagger.Client, src *dagger.Directory, platform dagger.Platform, goV
}).
WithDirectory("/src/pkg", src.Directory("pkg")).
WithDirectory("/src/apps", src.Directory("apps")).
WithDirectory("/src/.bingo", src.Directory(".bingo")).
WithDirectory("/src/.citools", src.Directory(".citools")).
WithFile("/src/Makefile", src.File("Makefile")).
WithWorkdir("/src").
@@ -31,7 +31,7 @@ type RepoGetter interface {
// Given a repository configuration, return it as a repository instance
// This will only error for un-recoverable system errors
// the repository instance may or may not be valid/healthy
AsRepository(ctx context.Context, cfg *provisioning.Repository) (repository.Repository, error)
RepositoryFromConfig(ctx context.Context, r *provisioning.Repository) (repository.Repository, error)
}
const loggerName = "provisioning-repository-controller"
@@ -223,7 +223,7 @@ func (rc *RepositoryController) handleDelete(ctx context.Context, obj *provision
// Process any finalizers
if len(obj.Finalizers) > 0 {
repo, err := rc.repoGetter.AsRepository(ctx, obj)
repo, err := rc.repoGetter.RepositoryFromConfig(ctx, obj)
if err != nil {
logger.Warn("unable to get repository for cleanup")
} else {
@@ -438,7 +438,7 @@ func (rc *RepositoryController) process(item *queueItem) error {
return nil
}
repo, err := rc.repoGetter.AsRepository(ctx, obj)
repo, err := rc.repoGetter.RepositoryFromConfig(ctx, obj)
if err != nil {
return fmt.Errorf("unable to create repository from configuration: %w", err)
}
+5 -4
View File
@@ -3,13 +3,14 @@ package provisioning
import (
"context"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-openapi/pkg/spec3"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-openapi/pkg/spec3"
)
type Extra interface {
@@ -17,7 +18,7 @@ type Extra interface {
UpdateStorage(storage map[string]rest.Storage) error
PostProcessOpenAPI(oas *spec3.OpenAPI) error
GetJobWorkers() []jobs.Worker
AsRepository(ctx context.Context, r *provisioning.Repository) (repository.Repository, error)
AsRepository(ctx context.Context, r *provisioning.Repository, secure repository.SecureValues) (repository.Repository, error)
RepositoryTypes() []provisioning.RepositoryType
Mutators() []controller.Mutator
}
+1 -1
View File
@@ -18,7 +18,7 @@ import (
type jobsConnector struct {
repoGetter RepoGetter
jobs jobs.Queue
historic jobs.History
historic jobs.HistoryReader
}
func (*jobsConnector) New() runtime.Object {

Some files were not shown because too many files have changed in this diff Show More