diff --git a/.betterer.results b/.betterer.results index 2c2d423f5f0..9557e0fff23 100644 --- a/.betterer.results +++ b/.betterer.results @@ -564,13 +564,6 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] ], - "packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] - ], - "packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] - ], "packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] @@ -1351,25 +1344,15 @@ exports[`better eslint`] = { [0, 0, 0, "\'@grafana/ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup\' import is restricted from being used by a pattern. Import from the public export instead.", "3"], [0, 0, 0, "\'@grafana/ui/src/components/JSONFormatter/JSONFormatter\' import is restricted from being used by a pattern. Import from the public export instead.", "4"], [0, 0, 0, "\'@grafana/ui/src/themes\' import is restricted from being used by a pattern. Import from the public export instead.", "5"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "6"], + [0, 0, 0, "\'@grafana/ui/src/utils/i18n\' import is restricted from being used by a pattern. Import from the public export instead.", "6"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "7"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "8"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "10"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "11"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "12"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "13"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "9"] ], "public/app/features/actions/ActionEditorModalContent.tsx:5381": [ [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "\'@grafana/ui/src/components/Modal/Modal\' import is restricted from being used by a pattern. Import from the public export instead.", "1"] ], - "public/app/features/actions/ActionsInlineEditor.tsx:5381": [ - [0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], - [0, 0, 0, "\'@grafana/ui/src/components/Modal/Modal\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], - [0, 0, 0, "\'@grafana/ui/src/themes\' import is restricted from being used by a pattern. Import from the public export instead.", "2"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "3"] - ], "public/app/features/actions/ParamsEditor.tsx:5381": [ [0, 0, 0, "\'@grafana/ui/src/components/IconButton/IconButton\' import is restricted from being used by a pattern. Import from the public export instead.", "0"], [0, 0, 0, "\'@grafana/ui/src/components/Input/Input\' import is restricted from being used by a pattern. Import from the public export instead.", "1"], @@ -5738,16 +5721,24 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "23"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "24"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "25"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "26"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "27"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "28"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "29"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "30"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "31"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "32"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "33"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "26"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "27"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "28"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "29"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "30"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "31"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "32"], + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "33"], [0, 0, 0, "No untranslated strings. Wrap text with ", "34"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "35"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "35"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "36"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "37"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "38"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "39"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "40"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "41"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "42"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "43"] ], "public/app/features/provisioning/DeleteRepositoryButton.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], diff --git a/pkg/apis/provisioning/v0alpha1/types.go b/pkg/apis/provisioning/v0alpha1/types.go index a421acc37a8..b6f42ded242 100644 --- a/pkg/apis/provisioning/v0alpha1/types.go +++ b/pkg/apis/provisioning/v0alpha1/types.go @@ -32,6 +32,17 @@ type S3RepositoryConfig struct { // TODO: How do we define access? Secrets? } +// Workflow used for changes in the repository. +// +enum +type Workflow string + +const ( + // BranchWorkflow creates a branch for changes + BranchWorkflow Workflow = "branch" + // PushWorkflow pushes changes directly the configured branch + PushWorkflow Workflow = "push" +) + type GitHubRepositoryConfig struct { // The owner of the repository (e.g. example in `example/test` or `https://github.com/example/test`). Owner string `json:"owner,omitempty"` @@ -46,9 +57,10 @@ type GitHubRepositoryConfig struct { // TODO: Do we want an SSH url instead maybe? // TODO: On-prem GitHub Enterprise support? - // Whether we should commit to change branches and use a Pull Request flow to achieve this. - // By default, this is false (i.e. we will commit straight to the main branch). - BranchWorkflow bool `json:"branchWorkflow,omitempty"` + // Workflow allowed for changes to the repository. + // The order is relevant for defining the precedence of the workflows. + // Possible values: pull-request, branch, push. + Workflows []Workflow `json:"workflows,omitempty"` // Whether we should show dashboard previews in the pull requests caused by the BranchWorkflow option. // By default, this is false (i.e. we will not create previews). @@ -110,8 +122,8 @@ const ( SyncTargetTypeInstance SyncTargetType = "instance" // Resources will be saved into a folder managed by this repository - // The folder k8s name will be the same as the repository k8s name // It will contain a copy of everything from the remote + // The folder k8s name will be the same as the repository k8s name SyncTargetTypeFolder SyncTargetType = "folder" ) diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index ab1fa9d7d2d..92a4bc345c0 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -93,6 +93,11 @@ 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.Workflows != nil { + in, out := &in.Workflows, &out.Workflows + *out = make([]Workflow, len(*in)) + copy(*out, *in) + } return } @@ -452,7 +457,7 @@ func (in *RepositorySpec) DeepCopyInto(out *RepositorySpec) { if in.GitHub != nil { in, out := &in.GitHub, &out.GitHub *out = new(GitHubRepositoryConfig) - **out = **in + (*in).DeepCopyInto(*out) } return } diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index f1f9d069391..dfb8863c800 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -266,11 +266,20 @@ func schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref common.Ref Format: "", }, }, - "branchWorkflow": { + "workflows": { SchemaProps: spec.SchemaProps{ - Description: "Whether we should commit to change branches and use a Pull Request flow to achieve this. By default, this is false (i.e. we will commit straight to the main branch).", - Type: []string{"boolean"}, - Format: "", + Description: "Workflow allowed for changes to the repository. The order is relevant for defining the precedence of the workflows. Possible values: pull-request, branch, push.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"branch", "push"}, + }, + }, + }, }, }, "generateDashboardPreviews": { @@ -1105,7 +1114,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref common.ReferenceCa }, "target": { SchemaProps: spec.SchemaProps{ - Description: "When syncing, where values are saved\n\nPossible enum values:\n - `\"folder\"` Resources will be saved into a folder managed by this repository The folder k8s name will be the same as the repository k8s name It will contain a copy of everything from the remote\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)", + 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)", Default: "", Type: []string{"string"}, Format: "", @@ -1636,7 +1645,7 @@ func schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref common.ReferenceCallb }, "target": { SchemaProps: spec.SchemaProps{ - Description: "Where values should be saved\n\nPossible enum values:\n - `\"folder\"` Resources will be saved into a folder managed by this repository The folder k8s name will be the same as the repository k8s name It will contain a copy of everything from the remote\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)", + Description: "Where values should be 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)", Default: "", Type: []string{"string"}, Format: "", diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index bd0478429cd..313713c794b 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,4 +1,5 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,FileList,Items +API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,GitHubRepositoryConfig,Workflows API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,HistoryList,Items API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Errors diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go b/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go index 1b1b9491cf7..e1afc1bf140 100644 --- a/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go +++ b/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go @@ -4,15 +4,19 @@ package v0alpha1 +import ( + provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + // GitHubRepositoryConfigApplyConfiguration represents a declarative configuration of the GitHubRepositoryConfig type for use // with apply. type GitHubRepositoryConfigApplyConfiguration struct { - Owner *string `json:"owner,omitempty"` - Repository *string `json:"repository,omitempty"` - Branch *string `json:"branch,omitempty"` - Token *string `json:"token,omitempty"` - BranchWorkflow *bool `json:"branchWorkflow,omitempty"` - GenerateDashboardPreviews *bool `json:"generateDashboardPreviews,omitempty"` + Owner *string `json:"owner,omitempty"` + Repository *string `json:"repository,omitempty"` + Branch *string `json:"branch,omitempty"` + Token *string `json:"token,omitempty"` + Workflows []provisioningv0alpha1.Workflow `json:"workflows,omitempty"` + GenerateDashboardPreviews *bool `json:"generateDashboardPreviews,omitempty"` } // GitHubRepositoryConfigApplyConfiguration constructs a declarative configuration of the GitHubRepositoryConfig type for use with @@ -53,11 +57,13 @@ func (b *GitHubRepositoryConfigApplyConfiguration) WithToken(value string) *GitH return b } -// WithBranchWorkflow sets the BranchWorkflow 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 BranchWorkflow field is set to the value of the last call. -func (b *GitHubRepositoryConfigApplyConfiguration) WithBranchWorkflow(value bool) *GitHubRepositoryConfigApplyConfiguration { - b.BranchWorkflow = &value +// WithWorkflows adds the given value to the Workflows 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 Workflows field. +func (b *GitHubRepositoryConfigApplyConfiguration) WithWorkflows(values ...provisioningv0alpha1.Workflow) *GitHubRepositoryConfigApplyConfiguration { + for i := range values { + b.Workflows = append(b.Workflows, values[i]) + } return b } diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 77b4d9b7d50..2939c5c7982 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -345,6 +345,13 @@ func (b *APIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admis if r.Spec.GitHub.Branch == "" { r.Spec.GitHub.Branch = "main" } + + if len(r.Spec.GitHub.Workflows) == 0 { + r.Spec.GitHub.Workflows = []provisioning.Workflow{ + provisioning.BranchWorkflow, + provisioning.PushWorkflow, + } + } } return nil diff --git a/pkg/registry/apis/provisioning/repository/github.go b/pkg/registry/apis/provisioning/repository/github.go index 29d4976c28b..6679b7e1d5c 100644 --- a/pkg/registry/apis/provisioning/repository/github.go +++ b/pkg/registry/apis/provisioning/repository/github.go @@ -78,8 +78,13 @@ func (r *githubRepository) Validate() (list field.ErrorList) { if gh.Token == "" { list = append(list, field.Required(field.NewPath("spec", "github", "token"), "a github access token is required")) } - if gh.GenerateDashboardPreviews && !gh.BranchWorkflow { - list = append(list, field.Forbidden(field.NewPath("spec", "github", "token"), "to generate dashboard previews, you must activate the branch workflow")) + + for _, w := range gh.Workflows { + switch w { + case provisioning.PushWorkflow, provisioning.BranchWorkflow: + default: + list = append(list, field.Invalid(field.NewPath("spec", "github", "workflow"), w, "invalid workflow")) + } } return list @@ -219,6 +224,9 @@ func (r *githubRepository) Create(ctx context.Context, path, ref string, data [] ref = r.config.Spec.GitHub.Branch } ctx, _ = r.logger(ctx, ref) + if err := r.isWorkflowAllowed(ref); err != nil { + return err + } if err := r.ensureBranchExists(ctx, ref); err != nil { return fmt.Errorf("create branch on create: %w", err) @@ -254,6 +262,9 @@ func (r *githubRepository) Update(ctx context.Context, path, ref string, data [] ref = r.config.Spec.GitHub.Branch } ctx, _ = r.logger(ctx, ref) + if err := r.isWorkflowAllowed(ref); err != nil { + return err + } if err := r.ensureBranchExists(ctx, ref); err != nil { return fmt.Errorf("create branch on update: %w", err) @@ -286,6 +297,14 @@ func (r *githubRepository) Update(ctx context.Context, path, ref string, data [] } func (r *githubRepository) Write(ctx context.Context, path string, ref string, data []byte, message string) error { + if ref == "" { + ref = r.config.Spec.GitHub.Branch + } + ctx, _ = r.logger(ctx, ref) + if err := r.isWorkflowAllowed(ref); err != nil { + return err + } + return writeWithReadThenCreateOrUpdate(ctx, r, path, ref, data, message) } @@ -294,6 +313,9 @@ func (r *githubRepository) Delete(ctx context.Context, path, ref, comment string ref = r.config.Spec.GitHub.Branch } ctx, _ = r.logger(ctx, ref) + if err := r.isWorkflowAllowed(ref); err != nil { + return err + } if err := r.ensureBranchExists(ctx, ref); err != nil { return fmt.Errorf("create branch on delete: %w", err) @@ -389,6 +411,25 @@ func (r *githubRepository) History(ctx context.Context, path, ref string) ([]pro return ret, nil } +func (r *githubRepository) isWorkflowAllowed(ref string) error { + cfg := r.config.Spec + + workflows := make(map[provisioning.Workflow]bool) + for _, w := range cfg.GitHub.Workflows { + workflows[w] = true + } + + if ref == cfg.GitHub.Branch && !workflows[provisioning.PushWorkflow] { + return apierrors.NewBadRequest("direct push not allowed") + } + + if ref != cfg.GitHub.Branch && !workflows[provisioning.BranchWorkflow] { + return apierrors.NewBadRequest("branch push not allowed") + } + + return nil +} + // basicGitBranchNameRegex is a regular expression to validate a git branch name // it does not cover all cases as positive lookaheads are not supported in Go's regexp var basicGitBranchNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-\_\/\.]+$`) diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index 186fc8d5931..474508f1453 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -2785,10 +2785,6 @@ "description": "The branch to use in the repository. By default, this is the main branch.", "type": "string" }, - "branchWorkflow": { - "description": "Whether we should commit to change branches and use a Pull Request flow to achieve this. By default, this is false (i.e. we will commit straight to the main branch).", - "type": "boolean" - }, "generateDashboardPreviews": { "description": "Whether we should show dashboard previews in the pull requests caused by the BranchWorkflow option. By default, this is false (i.e. we will not create previews). This option is a no-op if BranchWorkflow is `false` or default.", "type": "boolean" @@ -2804,6 +2800,18 @@ "token": { "description": "Token for accessing the repository.", "type": "string" + }, + "workflows": { + "description": "Workflow allowed for changes to the repository. The order is relevant for defining the precedence of the workflows. Possible values: pull-request, branch, push.", + "type": "array", + "items": { + "type": "string", + "default": "", + "enum": [ + "branch", + "push" + ] + } } } }, @@ -3422,7 +3430,7 @@ "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 The folder k8s name will be the same as the repository k8s name It will contain a copy of everything from the remote\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)", + "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", "default": "", "enum": [ @@ -3817,7 +3825,7 @@ "format": "int64" }, "target": { - "description": "Where values should be saved\n\nPossible enum values:\n - `\"folder\"` Resources will be saved into a folder managed by this repository The folder k8s name will be the same as the repository k8s name It will contain a copy of everything from the remote\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)", + "description": "Where values should be 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", "default": "", "enum": [ diff --git a/pkg/tests/apis/provisioning/testdata/github-example.json b/pkg/tests/apis/provisioning/testdata/github-example.json index d99ad7d6f71..03d7dc8b124 100644 --- a/pkg/tests/apis/provisioning/testdata/github-example.json +++ b/pkg/tests/apis/provisioning/testdata/github-example.json @@ -18,9 +18,9 @@ "owner": "grafana", "repository": "git-ui-sync-demo", "branch": "dummy-branch", - "branchWorkflow": true, + "workflows": ["push", "branch"], "generateDashboardPreviews": true, "token": "github_pat_dummy" } } -} \ No newline at end of file +} diff --git a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx index 66482cde9b6..97a8cc7ffce 100644 --- a/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx @@ -59,7 +59,6 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop control, setValue, } = useForm({ defaultValues }); - const [title, ref, workflow, comment] = watch(['title', 'ref', 'workflow', 'comment']); const { isDirty } = dashboard.state; const href = createPRLink(repositoryConfig, title, ref, comment); @@ -71,7 +70,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop if (isNew) { dashboard.setRepoName(defaultValues.repo); } - const prLink = workflow === WorkflowOption.Direct ? undefined : href; + const prLink = workflow !== 'push' ? href : undefined; dashboard.closeModal(); locationService.partial({ viewPanel: null, @@ -103,7 +102,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop description: description, }); - if (workflow === WorkflowOption.Direct) { + if (workflow === 'push') { ref = undefined; } @@ -183,11 +182,11 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop control={control} name={'workflow'} render={({ field: { ref, ...field } }) => { - return ; + return ; }} /> - {workflow === WorkflowOption.PullRequest && ( + {workflow === 'branch' && ( = [ + { label: `Push to ${config.github?.branch}`, value: 'push' }, + { label: 'Push to Different Branch', value: 'branch' }, ]; + + // Filter options based on the workflows in the config + return availableOptions.filter((option) => config.github?.workflows?.includes(option.value)); } diff --git a/public/app/features/provisioning/ConfigForm.tsx b/public/app/features/provisioning/ConfigForm.tsx index cdc8cae4fa0..7160d640c47 100644 --- a/public/app/features/provisioning/ConfigForm.tsx +++ b/public/app/features/provisioning/ConfigForm.tsx @@ -6,6 +6,7 @@ import { AppEvents } from '@grafana/data'; import { getAppEvents } from '@grafana/runtime'; import { Field, + FieldSet, Combobox, SecretInput, Input, @@ -15,12 +16,13 @@ import { ControlledCollapse, RadioButtonGroup, Stack, + MultiSelect, } from '@grafana/ui'; import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt'; import { Repository, RepositorySpec } from './api'; import { useCreateOrUpdateRepository } from './hooks'; -import { RepositoryFormData } from './types'; +import { RepositoryFormData, WorkflowOption } from './types'; import { dataToSpec, specToData } from './utils/data'; const typeOptions = ['GitHub', 'Local', 'S3'].map((label) => ({ label, value: label.toLowerCase() })); @@ -29,6 +31,11 @@ const targetOptions = [ { value: 'folder', label: 'Managed folder' }, ]; +const workflowOptions: Array<{ label: string; value: WorkflowOption }> = [ + { label: 'Push', value: 'push' }, + { label: 'Branch', value: 'branch' }, +]; + const appEvents = getAppEvents(); function getDefaultValues(repository?: RepositorySpec): RepositoryFormData { @@ -39,11 +46,13 @@ function getDefaultValues(repository?: RepositorySpec): RepositoryFormData { token: '', owner: '', repository: '', - branch: '', - branchWorkflow: true, + branch: 'main', + generateDashboardPreviews: true, + workflows: ['push', 'branch'], sync: { enabled: false, target: 'instance', + intervalSeconds: 60, }, readOnly: false, }; @@ -121,28 +130,8 @@ export function ConfigForm({ data }: ConfigFormProps) { {type === 'github' && ( - <> - -

- To create a new Access Token, navigate to{' '} - - Personal Access Tokens - {' '} - and create a click "Generate new token." -

- -

Ensure that your token has the following permissions:

- - For all repositories: -
public_repo, repo:status, repo_deployment, read:packages, read:user, user:email
- - For GitHub projects: -
read:org, read:project
- - An extra setting is required for private repositories: -
repo (Full control of private repositories)
-
- +
+ + +

+ To create a new Access Token, navigate to{' '} + + Personal Access Tokens + {' '} + and create a click "Generate new token." +

+ +

Ensure that your token has the following permissions:

+ + For all repositories: +
public_repo, repo:status, repo_deployment, read:packages, read:user, user:email
+ + For GitHub projects: +
read:org, read:project
+ + An extra setting is required for private repositories: +
repo (Full control of private repositories)
+
@@ -172,14 +181,28 @@ export function ConfigForm({ data }: ConfigFormProps) { - - + + { + return ( + onChange(value.map((v) => v.value))} + placeholder={'Select workflows'} + {...field} + /> + ); + }} + /> {/* The lint option is intentionally not presented here, as it's an experimental feature. */} - +
)} {type === 'local' && ( @@ -189,38 +212,45 @@ export function ConfigForm({ data }: ConfigFormProps) { )} {type === 's3' && ( - <> +
- +
)} - - { - return ( - - ); - }} - /> - - - - - - - +
+ + + + + { + return ( + + ); + }} + /> + + + + +
+
+ + + +