Provisioning: Use repository view rather than raw config (#103449)

pull/103293/head^2
Ryan McKinley 3 months ago committed by GitHub
parent 8cd6f837a5
commit 8dbaeac9da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      .betterer.results
  2. 6
      pkg/apis/provisioning/v0alpha1/settings.go
  3. 9
      pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
  4. 26
      pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
  5. 1
      pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
  6. 2
      pkg/registry/apis/provisioning/routes.go
  7. 21
      pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json
  8. 4
      public/app/api/clients/provisioning/endpoints.gen.ts
  9. 53
      public/app/features/browse-dashboards/components/NewProvisionedFolderForm.test.tsx
  10. 66
      public/app/features/browse-dashboards/components/NewProvisionedFolderForm.tsx
  11. 4
      public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx
  12. 24
      public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.test.tsx
  13. 10
      public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.tsx
  14. 13
      public/app/features/dashboard-scene/saving/provisioned/defaults.ts
  15. 52
      public/app/features/dashboard-scene/saving/provisioned/hooks.ts
  16. 2
      public/app/features/provisioning/Config/ConfigForm.tsx
  17. 2
      public/app/features/provisioning/HomePage.tsx
  18. 2
      public/app/features/provisioning/Wizard/WizardContent.tsx
  19. 7
      public/app/features/provisioning/hooks/index.ts
  20. 25
      public/app/features/provisioning/hooks/useGetResourceRepository.ts
  21. 82
      public/app/features/provisioning/hooks/useGetResourceRepositoryView.ts
  22. 14
      public/app/features/provisioning/hooks/useIsProvisionedNG.ts

@ -3098,15 +3098,6 @@ 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/hooks/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./useCreateOrUpdateRepositoryFile\`)", "0"],
[0, 0, 0, "Do not re-export imported variable (\`./useCreateOrUpdateRepository\`)", "1"],
[0, 0, 0, "Do not re-export imported variable (\`./useGetResourceRepository\`)", "2"],
[0, 0, 0, "Do not re-export imported variable (\`./useIsProvisionedNG\`)", "3"],
[0, 0, 0, "Do not re-export imported variable (\`./usePullRequestParam\`)", "4"],
[0, 0, 0, "Do not re-export imported variable (\`./useRepositoryJobs\`)", "5"],
[0, 0, 0, "Do not re-export imported variable (\`./useRepositoryList\`)", "6"]
],
"public/app/features/provisioning/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],

@ -25,12 +25,12 @@ type RepositoryView struct {
// Repository display
Title string `json:"title"`
// Edit options within the repository
ReadOnly bool `json:"readOnly"`
// The repository type
Type RepositoryType `json:"type"`
// When syncing, where values are saved
Target SyncTargetType `json:"target"`
// The supported workflows
Workflows []Workflow `json:"workflows"`
}

@ -590,6 +590,11 @@ func (in *RepositoryStatus) DeepCopy() *RepositoryStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RepositoryView) DeepCopyInto(out *RepositoryView) {
*out = *in
if in.Workflows != nil {
in, out := &in.Workflows, &out.Workflows
*out = make([]Workflow, len(*in))
copy(*out, *in)
}
return
}
@ -610,7 +615,9 @@ func (in *RepositoryViewList) DeepCopyInto(out *RepositoryViewList) {
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]RepositoryView, len(*in))
copy(*out, *in)
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

@ -1212,14 +1212,6 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref common.ReferenceCa
Format: "",
},
},
"readOnly": {
SchemaProps: spec.SchemaProps{
Description: "Edit options within the repository",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
"type": {
SchemaProps: spec.SchemaProps{
Description: "The repository type\n\nPossible enum values:\n - `\"github\"`\n - `\"local\"`",
@ -1238,8 +1230,24 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref common.ReferenceCa
Enum: []interface{}{"folder", "instance"},
},
},
"workflows": {
SchemaProps: spec.SchemaProps{
Description: "The supported workflows",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"branch", "write"},
},
},
},
},
},
},
Required: []string{"name", "title", "readOnly", "type", "target"},
Required: []string{"name", "title", "type", "target", "workflows"},
},
},
}

@ -6,6 +6,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provis
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Stats
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,Workflows
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryView,Workflows
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryViewList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,TestResults,Errors

@ -166,8 +166,8 @@ func (b *APIBuilder) handleSettings(w http.ResponseWriter, r *http.Request) {
Name: val.ObjectMeta.Name,
Title: val.Spec.Title,
Type: val.Spec.Type,
ReadOnly: len(val.Spec.Workflows) == 0,
Target: val.Spec.Sync.Target,
Workflows: val.Spec.Workflows,
}
}
w.Header().Set("Content-Type", "application/json")

@ -3229,9 +3229,9 @@
"required": [
"name",
"title",
"readOnly",
"type",
"target"
"target",
"workflows"
],
"properties": {
"name": {
@ -3239,11 +3239,6 @@
"type": "string",
"default": ""
},
"readOnly": {
"description": "Edit options within the repository",
"type": "boolean",
"default": false
},
"target": {
"description": "When syncing, where values are saved\n\nPossible enum values:\n - `\"folder\"` Resources will be saved into a folder managed by this repository It will contain a copy of everything from the remote The folder k8s name will be the same as the repository k8s name\n - `\"instance\"` Resources are saved in the global context Only one repository may specify the `instance` target When this exists, the UI will promote writing to the instance repo rather than the grafana database (where possible)",
"type": "string",
@ -3266,6 +3261,18 @@
"github",
"local"
]
},
"workflows": {
"description": "The supported workflows",
"type": "array",
"items": {
"type": "string",
"default": "",
"enum": [
"branch",
"write"
]
}
}
}
},

@ -1124,8 +1124,6 @@ export type WebhookResponse = {
export type RepositoryView = {
/** The k8s name for this repository */
name: string;
/** Edit options within the repository */
readOnly: boolean;
/** When syncing, where values are saved
Possible enum values:
@ -1140,6 +1138,8 @@ export type RepositoryView = {
- `"github"`
- `"local"` */
type: 'github' | 'local';
/** The supported workflows */
workflows: ('branch' | 'write')[];
};
export type RepositoryViewList = {
/** 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 */

@ -6,7 +6,8 @@ import { getAppEvents } from '@grafana/runtime';
import { useGetFolderQuery } from 'app/api/clients/folder';
import { useCreateRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
import { usePullRequestParam, useRepositoryList } from 'app/features/provisioning/hooks';
import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView';
import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam';
import { FolderDTO } from '../../../types';
@ -46,10 +47,15 @@ jest.mock('app/api/clients/folder', () => {
};
});
jest.mock('app/features/provisioning/hooks', () => {
jest.mock('app/features/provisioning/hooks/usePullRequestParam', () => {
return {
usePullRequestParam: jest.fn(),
useRepositoryList: jest.fn(),
};
});
jest.mock('app/features/provisioning/hooks/useGetResourceRepositoryView', () => {
return {
useGetResourceRepositoryView: jest.fn(),
};
});
@ -127,25 +133,19 @@ describe('NewProvisionedFolderForm', () => {
};
(getAppEvents as jest.Mock).mockReturnValue(mockAppEvents);
(useRepositoryList as jest.Mock).mockReturnValue([
[
{
metadata: {
(useGetResourceRepositoryView as jest.Mock).mockReturnValue({
isLoading: false,
repository: {
name: 'test-repo',
},
spec: {
title: 'Test Repository',
type: 'github',
github: {
url: 'https://github.com/grafana/grafana',
branch: 'main',
},
workflows: [{ name: 'default', path: 'workflows/default.yaml' }],
workflows: [{ name: 'default', path: 'workflows/default.json' }],
},
},
],
false,
]);
});
// Mock useGetFolderQuery
(useGetFolderQuery as jest.Mock).mockReturnValue({
@ -183,7 +183,9 @@ describe('NewProvisionedFolderForm', () => {
});
it('should show loading state when repository data is loading', () => {
(useRepositoryList as jest.Mock).mockReturnValue([[], true]);
(useGetResourceRepositoryView as jest.Mock).mockReturnValue({
isLoading: true,
});
setup();
@ -191,7 +193,10 @@ describe('NewProvisionedFolderForm', () => {
});
it('should show error when repository is not found', () => {
(useRepositoryList as jest.Mock).mockReturnValue([null, false]);
(useGetResourceRepositoryView as jest.Mock).mockReturnValue({
isLoading: false,
repository: undefined,
});
setup();
@ -423,24 +428,18 @@ describe('NewProvisionedFolderForm', () => {
it('should show read-only alert when repository has no workflows', () => {
// Mock repository with empty workflows array
(useRepositoryList as jest.Mock).mockReturnValue([
[
{
metadata: {
(useGetResourceRepositoryView as jest.Mock).mockReturnValue({
repository: {
name: 'test-repo',
},
spec: {
title: 'Test Repository',
type: 'github',
github: {
url: 'https://github.com/grafana/grafana',
branch: 'main',
},
workflows: [], // Empty workflows array
workflows: [],
},
},
],
false,
]);
});
setup();

@ -1,4 +1,3 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom-v5-compat';
@ -6,15 +5,15 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { Alert, Button, Field, Input, RadioButtonGroup, Spinner, Stack, TextArea } from '@grafana/ui';
import { useGetFolderQuery } from 'app/api/clients/folder';
import { useCreateRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning';
import { t, Trans } from 'app/core/internationalization';
import { AnnoKeyManagerIdentity, AnnoKeySourcePath, Resource } from 'app/features/apiserver/types';
import { AnnoKeySourcePath, Resource } from 'app/features/apiserver/types';
import { getDefaultWorkflow, getWorkflowOptions } from 'app/features/dashboard-scene/saving/provisioned/defaults';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
import { BranchValidationError } from 'app/features/provisioning/Shared/BranchValidationError';
import { PROVISIONING_URL } from 'app/features/provisioning/constants';
import { usePullRequestParam, useRepositoryList } from 'app/features/provisioning/hooks';
import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView';
import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam';
import { WorkflowOption } from 'app/features/provisioning/types';
import { validateBranchName } from 'app/features/provisioning/utils/git';
import { FolderDTO } from 'app/types';
@ -41,26 +40,12 @@ const initialFormValues: Partial<FormData> = {
};
export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: Props) {
const [items, isLoading] = useRepositoryList();
const { repository, folder, isLoading } = useGetResourceRepositoryView({ folderName: parentFolder?.uid });
const prURL = usePullRequestParam();
const navigate = useNavigate();
const [create, request] = useCreateRepositoryFilesWithPathMutation();
// Get k8s folder data, necessary to get parent folder path
const folderQuery = useGetFolderQuery(parentFolder ? { name: parentFolder.uid } : skipToken);
const repositoryName = folderQuery.data?.metadata?.annotations?.[AnnoKeyManagerIdentity];
if (!items && !isLoading) {
return (
<Alert
title={t('browse-dashboards.new-provisioned-folder-form.title-repository-not-found', 'Repository not found')}
severity="error"
/>
);
}
const repository = repositoryName ? items?.find((item) => item?.metadata?.name === repositoryName) : items?.[0];
const repositoryConfig = repository?.spec;
const isGitHub = Boolean(repositoryConfig?.github);
const isGitHub = Boolean(repository?.type === 'github');
const {
register,
@ -69,17 +54,17 @@ export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: P
formState: { errors },
control,
setValue,
} = useForm<FormData>({ defaultValues: { ...initialFormValues, workflow: getDefaultWorkflow(repositoryConfig) } });
} = useForm<FormData>({ defaultValues: { ...initialFormValues, workflow: getDefaultWorkflow(repository) } });
const [workflow, ref] = watch(['workflow', 'ref']);
useEffect(() => {
setValue('workflow', getDefaultWorkflow(repositoryConfig));
}, [repositoryConfig, setValue]);
setValue('workflow', getDefaultWorkflow(repository));
}, [repository, setValue]);
useEffect(() => {
const appEvents = getAppEvents();
if (request.isSuccess) {
if (request.isSuccess && repository) {
onSubmit();
appEvents.publish({
@ -100,7 +85,7 @@ export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: P
return;
}
let url = `${PROVISIONING_URL}/${repositoryName}/file/${request.data.path}`;
let url = `${PROVISIONING_URL}/${repository.name}/file/${request.data.path}`;
if (request.data.ref?.length) {
url += '?ref=' + request.data.ref;
}
@ -114,22 +99,21 @@ export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: P
],
});
}
}, [
request.isSuccess,
request.isError,
request.error,
onSubmit,
ref,
request.data,
workflow,
navigate,
repositoryName,
]);
}, [request.isSuccess, request.isError, request.error, onSubmit, ref, request.data, workflow, navigate, repository]);
if (isLoading || folderQuery.isLoading) {
if (isLoading) {
return <Spinner />;
}
if (!repository) {
return (
<Alert
title={t('browse-dashboards.new-provisioned-folder-form.title-repository-not-found', 'Repository not found')}
severity="error"
/>
);
}
const validateFolderName = async (folderName: string) => {
try {
await validationSrv.validateNewFolderName(folderName);
@ -143,11 +127,11 @@ export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: P
};
const doSave = async ({ ref, title, workflow, comment }: FormData) => {
const repoName = repository?.metadata?.name;
const repoName = repository?.name;
if (!title || !repoName) {
return;
}
const basePath = folderQuery.data?.metadata?.annotations?.[AnnoKeySourcePath] ?? '';
const basePath = folder?.metadata?.annotations?.[AnnoKeySourcePath] ?? '';
// Convert folder title to filename format (lowercase, replace spaces with hyphens)
const titleInFilenameFormat = title
@ -179,7 +163,7 @@ export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: P
return (
<form onSubmit={handleSubmit(doSave)}>
<Stack direction="column" gap={2}>
{!repositoryConfig?.workflows.length && (
{!repository?.workflows?.length && (
<Alert
title={t(
'browse-dashboards.new-provisioned-folder-form.title-this-repository-is-read-only',
@ -229,7 +213,7 @@ export function NewProvisionedFolderForm({ onSubmit, onCancel, parentFolder }: P
control={control}
name="workflow"
render={({ field: { ref, ...field } }) => (
<RadioButtonGroup {...field} options={getWorkflowOptions(repositoryConfig)} id={'folder-workflow'} />
<RadioButtonGroup {...field} options={getWorkflowOptions(repository)} id={'folder-workflow'} />
)}
/>
</Field>

@ -24,7 +24,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Save
if (!defaultValues) {
return null;
}
const { values, isNew, isGitHub, repositoryConfig } = defaultValues;
const { values, isNew, isGitHub, repository } = defaultValues;
return (
<SaveProvisionedDashboardForm
@ -35,7 +35,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Save
defaultValues={values}
loadedFromRef={loadedFromRef}
isGitHub={isGitHub}
repositoryConfig={repositoryConfig}
repository={repository}
/>
);
}

@ -48,15 +48,9 @@ jest.mock('app/features/provisioning/hooks/useCreateOrUpdateRepositoryFile', ()
};
});
jest.mock('app/features/provisioning/hooks/useGetResourceRepository', () => {
jest.mock('app/features/provisioning/hooks/useGetResourceRepositoryView', () => {
return {
useGetResourceRepository: jest.fn(),
};
});
jest.mock('app/features/provisioning/hooks/useRepositoryList', () => {
return {
useRepositoryList: jest.fn(),
useGetResourceRepositoryView: jest.fn(),
};
});
@ -133,15 +127,12 @@ function setup(props: Partial<Props> = {}) {
description: 'Test Description',
workflow: 'write',
},
repositoryConfig: {
repository: {
name: 'repo-xyz',
type: 'github',
workflows: ['write', 'branch'],
sync: { enabled: false, target: 'folder' },
title: 'Test Repository',
github: {
branch: 'main',
generateDashboardPreviews: false,
},
target: 'folder',
},
...props,
};
@ -401,10 +392,11 @@ describe('SaveProvisionedDashboardForm', () => {
it('should show read-only alert when repository has no workflows', () => {
setup({
repositoryConfig: {
repository: {
name: 'repo-abc',
type: 'github',
workflows: [],
sync: { enabled: false, target: 'folder' },
target: 'folder',
title: 'Read-only Repository',
},
});

@ -6,7 +6,7 @@ import { AppEvents, locationUtil } from '@grafana/data';
import { getAppEvents, locationService } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { Alert, Button, Field, Input, RadioButtonGroup, Stack, TextArea } from '@grafana/ui';
import { RepositorySpec } from 'app/api/clients/provisioning';
import { RepositoryView } from 'app/api/clients/provisioning';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { t, Trans } from 'app/core/internationalization';
import kbn from 'app/core/utils/kbn';
@ -43,7 +43,7 @@ export interface Props extends SaveProvisionedDashboardProps {
isNew: boolean;
defaultValues: FormData;
isGitHub: boolean;
repositoryConfig?: RepositorySpec;
repository?: RepositoryView;
loadedFromRef?: string;
}
@ -54,7 +54,7 @@ export function SaveProvisionedDashboardForm({
changeInfo,
isNew,
loadedFromRef,
repositoryConfig,
repository,
isGitHub,
}: Props) {
const navigate = useNavigate();
@ -154,12 +154,12 @@ export function SaveProvisionedDashboardForm({
});
};
const workflowOptions = getWorkflowOptions(repositoryConfig, loadedFromRef);
const workflowOptions = getWorkflowOptions(repository, loadedFromRef);
return (
<form onSubmit={handleSubmit(handleFormSubmit)} name="save-provisioned-form">
<Stack direction="column" gap={2}>
{!repositoryConfig?.workflows.length && (
{!repository?.workflows?.length && (
<Alert
title={t(
'dashboard-scene.save-provisioned-dashboard-form.title-this-repository-is-read-only',

@ -1,22 +1,21 @@
import { RepositorySpec } from 'app/api/clients/provisioning';
import { RepositoryView } from 'app/api/clients/provisioning';
import { WorkflowOption } from 'app/features/provisioning/types';
export function getDefaultWorkflow(config?: RepositorySpec) {
export function getDefaultWorkflow(config?: RepositoryView) {
return config?.workflows?.[0];
}
export function getWorkflowOptions(config?: RepositorySpec, ref?: string) {
export function getWorkflowOptions(config?: RepositoryView, ref?: string) {
if (!config) {
return [];
}
if (config.local?.path) {
return [{ label: `Write to ${config.local.path}`, value: 'write' }];
if (config.type === 'local') {
return [{ label: `Save`, value: 'write' }];
}
let branch = ref ?? config.github?.branch;
const availableOptions: Array<{ label: string; value: WorkflowOption }> = [
{ label: `Push to ${branch ?? 'main'}`, value: 'write' },
{ label: ref ? `Push to ${ref}` : 'Save', value: 'write' },
{ label: 'Push to different branch', value: 'branch' },
];

@ -1,9 +1,5 @@
import { skipToken } from '@reduxjs/toolkit/query/react';
import { useGetFolderQuery } from 'app/api/clients/folder';
import { AnnoKeyManagerIdentity, AnnoKeyManagerKind, AnnoKeySourcePath } from 'app/features/apiserver/types';
import { useGetResourceRepository } from 'app/features/provisioning/hooks/useGetResourceRepository';
import { useRepositoryList } from 'app/features/provisioning/hooks/useRepositoryList';
import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView';
import { DashboardMeta } from 'app/types';
import { getDefaultWorkflow } from './defaults';
@ -21,14 +17,13 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription }: Use
const managerKind = annotations?.[AnnoKeyManagerKind];
const managerIdentity = annotations?.[AnnoKeyManagerIdentity];
const sourcePath = annotations?.[AnnoKeySourcePath];
const repositoryConfig = useConfig({ folderUid: meta.folderUid, managerKind, managerIdentity });
const repository = repositoryConfig?.spec;
const { repository, folder, isLoading } = useGetResourceRepositoryView({
name: managerKind === 'repo' ? managerIdentity : undefined,
folderName: meta.folderUid,
});
const timestamp = generateTimestamp();
// Get folder data to retrieve the folder path
const folderQuery = useGetFolderQuery(meta.folderUid ? { name: meta.folderUid } : skipToken);
const folderPath = meta.folderUid ? (folderQuery.data?.metadata?.annotations?.[AnnoKeySourcePath] ?? '') : '';
const folderPath = folder?.metadata?.annotations?.[AnnoKeySourcePath];
const dashboardPath = generatePath({
timestamp,
@ -37,7 +32,7 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription }: Use
folderPath,
});
if (folderQuery.isLoading || !repositoryConfig) {
if (isLoading || !repository) {
return null;
}
@ -45,7 +40,7 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription }: Use
values: {
ref: `dashboard/${timestamp}`,
path: dashboardPath,
repo: managerIdentity || repositoryConfig?.metadata?.name || '',
repo: managerIdentity || repository?.name || '',
comment: '',
folder: {
uid: meta.folderUid,
@ -56,36 +51,7 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription }: Use
workflow: getDefaultWorkflow(repository),
},
isNew: !meta.k8s?.name,
repositoryConfig: repository,
isGitHub: repository?.type === 'github',
repository,
};
}
type UseConfigArgs = {
folderUid?: string;
managerKind?: string;
managerIdentity?: string;
};
const useConfig = ({ folderUid, managerKind, managerIdentity }: UseConfigArgs) => {
const repositoryConfig = useGetResourceRepository({
name: managerKind === 'repo' ? managerIdentity : undefined,
folderUid,
});
const [items, isLoading] = useRepositoryList(repositoryConfig ? skipToken : undefined);
if (repositoryConfig) {
return repositoryConfig;
}
if (isLoading) {
return null;
}
const instanceConfig = items?.find((repo) => repo.spec?.sync.target === 'instance');
if (instanceConfig) {
return instanceConfig;
}
// Return the config, which targets the folder
return items?.find((repo) => repo?.metadata?.name === folderUid);
};

@ -20,7 +20,7 @@ import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt';
import { t } from 'app/core/internationalization';
import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo';
import { useCreateOrUpdateRepository } from '../hooks';
import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository';
import { RepositoryFormData, WorkflowOption } from '../types';
import { dataToSpec, specToData } from '../utils/data';

@ -8,7 +8,7 @@ import { t, Trans } from 'app/core/internationalization';
import GettingStarted from './GettingStarted/GettingStarted';
import GettingStartedPage from './GettingStarted/GettingStartedPage';
import { FolderRepositoryList } from './Shared/FolderRepositoryList';
import { useRepositoryList } from './hooks';
import { useRepositoryList } from './hooks/useRepositoryList';
enum TabSelection {
Repositories = 'repositories',

@ -14,7 +14,7 @@ import {
import { t } from 'app/core/internationalization';
import { PROVISIONING_URL } from '../constants';
import { useCreateOrUpdateRepository } from '../hooks';
import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository';
import { StepStatus } from '../hooks/useStepStatus';
import { dataToSpec } from '../utils/data';

@ -1,7 +0,0 @@
export { useCreateOrUpdateRepository } from './useCreateOrUpdateRepository';
export { useCreateOrUpdateRepositoryFile } from './useCreateOrUpdateRepositoryFile';
export { useGetResourceRepository } from './useGetResourceRepository';
export { useIsProvisionedNG } from './useIsProvisionedNG';
export { usePullRequestParam } from './usePullRequestParam';
export { useRepositoryJobs } from './useRepositoryJobs';
export { useRepositoryList } from './useRepositoryList';

@ -1,25 +0,0 @@
import { skipToken } from '@reduxjs/toolkit/query/react';
import { useGetFolderQuery } from '../../../api/clients/folder';
import { AnnoKeyManagerIdentity } from '../../apiserver/types';
import { useRepositoryList } from './useRepositoryList';
interface GetResourceRepositoryArgs {
name?: string;
folderUid?: string;
}
export const useGetResourceRepository = ({ name, folderUid }: GetResourceRepositoryArgs) => {
const [items, isLoading] = useRepositoryList(name || !folderUid ? skipToken : undefined);
// Get the folder data from API to get the repository data for nested folders
const folderQuery = useGetFolderQuery(name || !folderUid ? skipToken : { name: folderUid });
const repoName = name || folderQuery.data?.metadata?.annotations?.[AnnoKeyManagerIdentity];
if (!items?.length || isLoading || !repoName) {
return undefined;
}
return items.find((repo) => repo.metadata?.name === repoName);
};

@ -0,0 +1,82 @@
import { skipToken } from '@reduxjs/toolkit/query/react';
import { Folder, useGetFolderQuery } from 'app/api/clients/folder';
import { RepositoryView, useGetFrontendSettingsQuery } from 'app/api/clients/provisioning';
import { AnnoKeyManagerIdentity } from 'app/features/apiserver/types';
interface GetResourceRepositoryArgs {
name?: string; // the repository name
folderName?: string; // folder we are targeting
}
interface RepositoryViewData {
repository?: RepositoryView;
folder?: Folder;
isLoading?: boolean;
isInstanceManaged: boolean;
}
// This is safe to call as a viewer (you do not need full access to the Repository configs)
export const useGetResourceRepositoryView = ({ name, folderName }: GetResourceRepositoryArgs): RepositoryViewData => {
const { data: settingsData, isLoading: isSettingsLoading } = useGetFrontendSettingsQuery();
const skipFolderQuery = name || !folderName;
const { data: folder, isLoading: isFolderLoading } = useGetFolderQuery(
skipFolderQuery ? skipToken : { name: folderName }
);
if (isSettingsLoading || isFolderLoading) {
return { isLoading: true, isInstanceManaged: false };
}
const items = settingsData?.items ?? [];
if (!items.length) {
return { folder, isInstanceManaged: false };
}
const instanceRepo = items.find((repo) => repo.target === 'instance');
const isInstanceManaged = Boolean(instanceRepo);
if (name) {
const repository = items.find((repo) => repo.name === name);
if (repository) {
return {
repository,
folder,
isInstanceManaged,
};
}
}
// Find the matching folder repository
if (folderName) {
// For root values it will be the same
let repository = items.find((repo) => repo.name === folderName);
if (repository) {
return {
repository,
folder,
isInstanceManaged,
};
}
// For nested folders we need to see what the folder thinks
const annotatedFolderName = folder?.metadata?.annotations?.[AnnoKeyManagerIdentity];
if (annotatedFolderName && name) {
repository = items.find((repo) => repo.name === annotatedFolderName);
if (repository) {
return {
repository,
folder,
isInstanceManaged,
};
}
}
}
return {
repository: instanceRepo,
folder,
isInstanceManaged,
};
};

@ -1,23 +1,17 @@
import { config } from '@grafana/runtime';
import { useGetFrontendSettingsQuery } from 'app/api/clients/provisioning';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { useGetResourceRepository } from './useGetResourceRepository';
import { useGetResourceRepositoryView } from './useGetResourceRepositoryView';
export function useIsProvisionedNG(dashboard: DashboardScene): boolean {
const params = new URLSearchParams(window.location.search);
const folderUid = params.get('folderUid') || undefined;
const folderName = params.get('folderUid') || undefined;
const folderRepository = useGetResourceRepository({ folderUid });
const { data } = useGetFrontendSettingsQuery();
const { repository, isInstanceManaged } = useGetResourceRepositoryView({ folderName });
if (!config.featureToggles.provisioning) {
return false;
}
return (
dashboard.isManagedRepository() ||
Boolean(folderRepository) ||
Boolean(data?.items.some((item) => item.target === 'instance'))
);
return dashboard.isManagedRepository() || Boolean(repository) || isInstanceManaged;
}

Loading…
Cancel
Save