diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
index 8a3def39e8d..d9e28d6799c 100644
--- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
@@ -88,6 +88,11 @@ func (in *ErrorDetails) DeepCopy() *ErrorDetails {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExportJobOptions) DeepCopyInto(out *ExportJobOptions) {
*out = *in
+ if in.Resources != nil {
+ in, out := &in.Resources, &out.Resources
+ *out = make([]ResourceRef, len(*in))
+ copy(*out, *in)
+ }
return
}
@@ -425,7 +430,7 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
if in.Push != nil {
in, out := &in.Push, &out.Push
*out = new(ExportJobOptions)
- **out = **in
+ (*in).DeepCopyInto(*out)
}
if in.Pull != nil {
in, out := &in.Pull, &out.Pull
diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
index 9a4a99d703a..68530ef5eef 100644
--- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
@@ -258,9 +258,25 @@ func schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref common.Reference
Format: "",
},
},
+ "resources": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Resources to export This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the resources.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef"),
+ },
+ },
+ },
+ },
+ },
},
},
},
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef"},
}
}
diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
index c67b1462a9e..ad68423349a 100644
--- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
@@ -1,5 +1,6 @@
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Resources
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ExportJobOptions,Resources
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,FileList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,HistoryList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors
diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go
index f1d543cdd90..b4c2c6d403f 100644
--- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go
+++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/exportjoboptions.go
@@ -7,10 +7,11 @@ package v0alpha1
// ExportJobOptionsApplyConfiguration represents a declarative configuration of the ExportJobOptions type for use
// with apply.
type ExportJobOptionsApplyConfiguration struct {
- Message *string `json:"message,omitempty"`
- Folder *string `json:"folder,omitempty"`
- Branch *string `json:"branch,omitempty"`
- Path *string `json:"path,omitempty"`
+ Message *string `json:"message,omitempty"`
+ Folder *string `json:"folder,omitempty"`
+ Branch *string `json:"branch,omitempty"`
+ Path *string `json:"path,omitempty"`
+ Resources []ResourceRefApplyConfiguration `json:"resources,omitempty"`
}
// ExportJobOptionsApplyConfiguration constructs a declarative configuration of the ExportJobOptions type for use with
@@ -50,3 +51,16 @@ func (b *ExportJobOptionsApplyConfiguration) WithPath(value string) *ExportJobOp
b.Path = &value
return b
}
+
+// WithResources adds the given value to the Resources 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 Resources field.
+func (b *ExportJobOptionsApplyConfiguration) WithResources(values ...*ResourceRefApplyConfiguration) *ExportJobOptionsApplyConfiguration {
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithResources")
+ }
+ b.Resources = append(b.Resources, *values[i])
+ }
+ return b
+}
diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts
index 7ef1fc4fc91..e650453a2a7 100644
--- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts
+++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts
@@ -1108,6 +1108,8 @@ export type ExportJobOptions = {
message?: string;
/** FIXME: we should validate this in admission hooks Prefix in target file system */
path?: string;
+ /** Resources to export This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the resources. */
+ resources?: ResourceRef[];
};
export type JobSpec = {
/** Possible enum values:
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 f99b8f60738..b0eff371ae7 100644
--- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json
+++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json
@@ -3288,6 +3288,18 @@
"path": {
"description": "FIXME: we should validate this in admission hooks Prefix in target file system",
"type": "string"
+ },
+ "resources": {
+ "description": "Resources to export This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the resources.",
+ "type": "array",
+ "items": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceRef"
+ }
+ ]
+ }
}
}
},
diff --git a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
index 1c89c3c6dd4..927a8ea7a79 100644
--- a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
+++ b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
@@ -6,8 +6,10 @@ import { Button, Drawer, Stack, Text } from '@grafana/ui';
import { appEvents } from 'app/core/app_events';
import { ManagerKind } from 'app/features/apiserver/types';
import { BulkDeleteProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource';
+import { BulkPushProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkPushProvisionedResource';
import { BulkMoveProvisionedResource } from 'app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource';
import { useSelectionProvisioningStatus } from 'app/features/provisioning/hooks/useSelectionProvisioningStatus';
+import { useSelectionUnmanagedStatus } from 'app/features/provisioning/hooks/useSelectionUnmanagedStatus';
import { useSearchStateManager } from 'app/features/search/state/SearchStateManager';
import { ShowModalReactEvent } from 'app/types/events';
import { FolderDTO } from 'app/types/folders';
@@ -33,6 +35,7 @@ export interface Props {
export function BrowseActions({ folderDTO }: Props) {
const [showBulkDeleteProvisionedResource, setShowBulkDeleteProvisionedResource] = useState(false);
const [showBulkMoveProvisionedResource, setShowBulkMoveProvisionedResource] = useState(false);
+ const [showBulkPushProvisionedResource, setShowBulkPushProvisionedResource] = useState(false);
const dispatch = useDispatch();
const selectedItems = useActionSelectionState();
@@ -47,6 +50,7 @@ export function BrowseActions({ folderDTO }: Props) {
selectedItems,
folderDTO?.managedBy === ManagerKind.Repo
);
+ const { hasUnmanaged, isLoading: isLoadingUnmanaged } = useSelectionUnmanagedStatus(selectedItems);
const isSearching = stateManager.hasSearchFilters();
@@ -140,10 +144,25 @@ export function BrowseActions({ folderDTO }: Props) {
);
+ // Check if any dashboards are selected (export only supports dashboards, not folders)
+ const hasSelectedDashboards =
+ Object.keys(selectedItems.dashboard || {}).filter((uid) => selectedItems.dashboard[uid]).length > 0;
+
+ const pushButton = (
+
+ );
+
return (
<>
{moveButton}
+ {provisioningEnabled && pushButton}