diff --git a/public/app/features/provisioning/Config/ConfigForm.tsx b/public/app/features/provisioning/Config/ConfigForm.tsx
index c0ba526533f..0ba33a4e5db 100644
--- a/public/app/features/provisioning/Config/ConfigForm.tsx
+++ b/public/app/features/provisioning/Config/ConfigForm.tsx
@@ -4,6 +4,7 @@ import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom-v5-compat';
import { t } from '@grafana/i18n';
+import { reportInteraction } from '@grafana/runtime';
import {
Button,
Checkbox,
@@ -118,12 +119,19 @@ export function ConfigForm({ data }: ConfigFormProps) {
useEffect(() => {
if (request.isSuccess) {
const formData = getValues();
+
+ reportInteraction('grafana_provisioning_repository_updated', {
+ repositoryName: repositoryName ?? 'unknown',
+ repositoryType: formData.type,
+ target: formData.sync?.target ?? 'unknown',
+ });
+
reset(formData);
setTimeout(() => {
navigate('/admin/provisioning');
}, 300);
}
- }, [request.isSuccess, reset, getValues, navigate]);
+ }, [request.isSuccess, reset, getValues, navigate, repositoryName]);
const onSubmit = async (form: RepositoryFormData) => {
setIsLoading(true);
diff --git a/public/app/features/provisioning/Repository/DeleteRepositoryButton.tsx b/public/app/features/provisioning/Repository/DeleteRepositoryButton.tsx
index 51186523d51..23ea3943268 100644
--- a/public/app/features/provisioning/Repository/DeleteRepositoryButton.tsx
+++ b/public/app/features/provisioning/Repository/DeleteRepositoryButton.tsx
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { t, Trans } from '@grafana/i18n';
+import { reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal, Dropdown, Icon, Menu, Stack } from '@grafana/ui';
import {
Repository,
@@ -44,6 +45,15 @@ export function DeleteRepositoryButton({ name, repository, redirectTo }: Props)
};
await replaceRepository({ name, repository: updatedRepository });
}
+
+ reportInteraction('grafana_provisioning_repository_deleted', {
+ repositoryName: name,
+ repositoryType: repository?.spec?.type ?? 'unknown',
+ deleteAction: selectedAction,
+ target: repository?.spec?.sync?.target ?? 'unknown',
+ workflows: repository?.spec?.workflows ?? [],
+ });
+
deleteRepository({ name });
}, [deleteRepository, replaceRepository, name, selectedAction, repository]);
diff --git a/public/app/features/provisioning/Repository/RepositoryActions.tsx b/public/app/features/provisioning/Repository/RepositoryActions.tsx
index 70fa314bee1..edadb2b538e 100644
--- a/public/app/features/provisioning/Repository/RepositoryActions.tsx
+++ b/public/app/features/provisioning/Repository/RepositoryActions.tsx
@@ -1,4 +1,5 @@
import { t, Trans } from '@grafana/i18n';
+import { reportInteraction } from '@grafana/runtime';
import { Badge, Button, LinkButton, Stack } from '@grafana/ui';
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
@@ -33,7 +34,17 @@ export function RepositoryActions({ repository }: RepositoryActionsProps) {
)}
-
+ {
+ reportInteraction('grafana_provisioning_repository_settings_opened', {
+ repositoryName: name,
+ repositoryType: repository.spec?.type ?? 'unknown',
+ });
+ }}
+ >
Settings
diff --git a/public/app/features/provisioning/Repository/RepositoryCard.tsx b/public/app/features/provisioning/Repository/RepositoryCard.tsx
index 545777b9f4f..8a8205e9a2f 100644
--- a/public/app/features/provisioning/Repository/RepositoryCard.tsx
+++ b/public/app/features/provisioning/Repository/RepositoryCard.tsx
@@ -1,6 +1,7 @@
import { ReactNode } from 'react';
import { t, Trans } from '@grafana/i18n';
+import { reportInteraction } from '@grafana/runtime';
import { Stack, Text, TextLink, Icon, Card, LinkButton, Badge } from '@grafana/ui';
import { Repository, ResourceCount } from 'app/api/clients/provisioning/v0alpha1';
@@ -103,7 +104,18 @@ export function RepositoryCard({ repository }: Props) {
View
-
+ {
+ reportInteraction('grafana_provisioning_repository_settings_opened', {
+ repositoryName: name,
+ repositoryType: spec?.type ?? 'unknown',
+ });
+ }}
+ >
Settings
diff --git a/public/app/features/provisioning/Repository/SyncRepository.tsx b/public/app/features/provisioning/Repository/SyncRepository.tsx
index bbf4ff01fb4..36a62d44d1f 100644
--- a/public/app/features/provisioning/Repository/SyncRepository.tsx
+++ b/public/app/features/provisioning/Repository/SyncRepository.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { Trans, t } from '@grafana/i18n';
+import { reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal } from '@grafana/ui';
import { Repository, useCreateRepositoryJobsMutation } from 'app/api/clients/provisioning/v0alpha1';
@@ -23,6 +24,11 @@ export function SyncRepository({ repository }: Props) {
if (!name) {
return;
}
+ reportInteraction('grafana_provisioning_repository_pull_triggered', {
+ repositoryName: name,
+ repositoryType: repository.spec?.type ?? 'unknown',
+ target: repository.spec?.sync.target ?? 'unknown',
+ });
createJob({
name,
jobSpec: {
diff --git a/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx b/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx
index e61b9f31e21..cdf80faec33 100644
--- a/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx
+++ b/public/app/features/provisioning/Shared/ConnectRepositoryButton.tsx
@@ -33,7 +33,9 @@ export function ConnectRepositoryButton({ items }: Props) {
key={config.type}
icon={config.icon}
label={config.label}
- onClick={() => navigate(`${CONNECT_URL}/${config.type}`)}
+ onClick={() => {
+ navigate(`${CONNECT_URL}/${config.type}`);
+ }}
/>
);
})}
diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx
index 5eb4fd174ee..01b8c5436c3 100644
--- a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx
+++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx
@@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents, GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
-import { getAppEvents, isFetchError } from '@grafana/runtime';
+import { getAppEvents, isFetchError, reportInteraction } from '@grafana/runtime';
import { Box, Button, ConfirmModal, Stack, Text, useStyles2 } from '@grafana/ui';
import { RepositoryViewList, useDeleteRepositoryMutation } from 'app/api/clients/provisioning/v0alpha1';
import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt';
@@ -14,7 +14,7 @@ import { getDefaultValues } from '../Config/defaults';
import { ProvisioningAlert } from '../Shared/ProvisioningAlert';
import { PROVISIONING_URL } from '../constants';
import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository';
-import { dataToSpec } from '../utils/data';
+import { dataToSpec, getWorkflows } from '../utils/data';
import { getFormErrors } from '../utils/getFormErrors';
import { BootstrapStep } from './BootstrapStep';
@@ -176,6 +176,11 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({
if (previousStepIndex >= 0) {
const previousStep = steps[previousStepIndex];
+ reportInteraction('grafana_provisioning_wizard_previous_clicked', {
+ fromStep: activeStep,
+ toStep: previousStep.id,
+ repositoryType: repoType,
+ });
setActiveStep(previousStep.id);
// Remove current step from completed steps when going back
setCompletedSteps((prev) => prev.filter((step) => step !== activeStep));
@@ -209,6 +214,10 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({
const handleConfirmCancel = () => {
setShowCancelConfirmation(false);
+ reportInteraction('grafana_provisioning_wizard_cancelled', {
+ cancelledAtStep: activeStep,
+ repositoryType: repoType,
+ });
handleRepositoryDeletion(repoName);
};
@@ -254,6 +263,12 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({
// Only navigate to provisioning URL if we're on the actual last step
if (isLastStep) {
+ const formData = getValues();
+ reportInteraction('grafana_provisioning_repository_created', {
+ repositoryType: repoType,
+ target: syncTarget,
+ workflowsEnabled: getWorkflows(formData.repository),
+ });
navigate(PROVISIONING_URL);
} else {
let nextStepIndex = currentStepIndex + 1;
@@ -274,6 +289,12 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({
return;
}
+ reportInteraction('grafana_provisioning_wizard_step_completed', {
+ step: activeStep,
+ repositoryType: repoType,
+ target: syncTarget,
+ });
+
setActiveStep(steps[nextStepIndex].id);
setCompletedSteps((prev) => [...new Set([...prev, activeStep])]);
setStepStatusInfo({ status: 'idle' });
diff --git a/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx b/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx
index 5c0c6f5de32..ff3743fc45b 100644
--- a/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx
+++ b/public/app/features/provisioning/components/BulkActions/BulkDeleteProvisionedResource.tsx
@@ -3,7 +3,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents } from '@grafana/runtime';
+import { getAppEvents, reportInteraction } from '@grafana/runtime';
import { Box, Button, Stack } from '@grafana/ui';
import { Job, RepositoryView } from 'app/api/clients/provisioning/v0alpha1';
import { DescendantCount } from 'app/features/browse-dashboards/components/BrowseActions/DescendantCount';
@@ -47,6 +47,17 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions
const resources = collectSelectedItems(selectedItems);
+ const folderCount = Object.keys(selectedItems.folder || {}).length;
+ const dashboardCount = Object.keys(selectedItems.dashboard || {}).length;
+ reportInteraction('grafana_provisioning_bulk_delete_submitted', {
+ workflow: data.workflow,
+ repositoryName: repository.name ?? 'unknown',
+ repositoryType: repository.type ?? 'unknown',
+ resourceCount: resources.length,
+ folderCount,
+ dashboardCount,
+ });
+
// Create the delete job spec
const jobSpec: DeleteJobSpec = {
action: 'delete',
diff --git a/public/app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource.tsx b/public/app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource.tsx
index 6e553ed760c..7f07a6184e7 100644
--- a/public/app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource.tsx
+++ b/public/app/features/provisioning/components/BulkActions/BulkMoveProvisionedResource.tsx
@@ -4,7 +4,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents } from '@grafana/runtime';
+import { getAppEvents, reportInteraction } from '@grafana/runtime';
import { Box, Button, Field, Stack } from '@grafana/ui';
import { useGetFolderQuery } from 'app/api/clients/folder/v1beta1';
import { RepositoryView, Job } from 'app/api/clients/provisioning/v0alpha1';
@@ -87,6 +87,13 @@ function FormContent({ initialValues, selectedItems, repository, workflowOptions
return;
}
+ reportInteraction('grafana_provisioning_bulk_move_submitted', {
+ workflow: data.workflow,
+ repositoryName: repository.name ?? 'unknown',
+ repositoryType: repository.type ?? 'unknown',
+ resourceCount: resources.length,
+ });
+
// Create the move job spec
const jobSpec: MoveJobSpec = {
action: 'move',
diff --git a/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx
index 0d41a378d61..743975947a0 100644
--- a/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx
+++ b/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents } from '@grafana/runtime';
+import { getAppEvents, reportInteraction } from '@grafana/runtime';
import { Button, Drawer, Stack } from '@grafana/ui';
import { Job, RepositoryView, useDeleteRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
@@ -80,6 +80,12 @@ export function DeleteProvisionedDashboardForm({
return;
}
+ reportInteraction('grafana_provisioning_dashboard_delete_submitted', {
+ workflow,
+ repositoryName: repo,
+ repositoryType: repository?.type ?? 'unknown',
+ });
+
// Branch workflow: use /files API for direct file operations
if (workflow === 'branch') {
const branchRef = ref;
diff --git a/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx
index 3968484ceb2..637d0861882 100644
--- a/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx
+++ b/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx
@@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents } from '@grafana/runtime';
+import { getAppEvents, reportInteraction } from '@grafana/runtime';
import { Alert, Button, Drawer, Field, Input, Spinner, Stack } from '@grafana/ui';
import { useGetFolderQuery } from 'app/api/clients/folder/v1beta1';
import {
@@ -123,6 +123,12 @@ export function MoveProvisionedDashboardForm({
return;
}
+ reportInteraction('grafana_provisioning_dashboard_move_submitted', {
+ workflow,
+ repositoryName: repo,
+ repositoryType: repository?.type ?? 'unknown',
+ });
+
// Branch workflow: use /files API for direct file operations
if (workflow === 'branch') {
if (!currentFileData?.resource?.file) {
diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx
index 368076cafae..f278c2e436c 100644
--- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx
+++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents, locationUtil } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents, locationService } from '@grafana/runtime';
+import { getAppEvents, locationService, reportInteraction } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { Button, Field, Input, Stack, TextArea } from '@grafana/ui';
import { RepositoryView, Unstructured } from 'app/api/clients/provisioning/v0alpha1';
@@ -133,7 +133,15 @@ export function SaveProvisionedDashboardForm({
});
// Submit handler for saving the form data
- const handleFormSubmit = async ({ title, description, repo, path, comment, ref }: ProvisionedDashboardFormData) => {
+ const handleFormSubmit = async ({
+ title,
+ description,
+ repo,
+ path,
+ comment,
+ ref,
+ folder,
+ }: ProvisionedDashboardFormData) => {
// Validate required fields
if (!repo || !path) {
console.error('Missing required fields for saving:', { repo, path });
@@ -155,6 +163,12 @@ export function SaveProvisionedDashboardForm({
copyTags: true,
});
+ reportInteraction('grafana_provisioning_dashboard_save_submitted', {
+ workflow,
+ repositoryName: repo,
+ repositoryType: repository?.type ?? 'unknown',
+ });
+
createOrUpdateFile({
// Skip adding ref to the default branch request
ref: ref === repository?.branch ? undefined : ref,
diff --git a/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx b/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx
index 2c76f5c877f..102cacbf7d4 100644
--- a/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx
+++ b/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents } from '@grafana/runtime';
+import { getAppEvents, reportInteraction } from '@grafana/runtime';
import { Box, Button, Stack } from '@grafana/ui';
import { Folder } from 'app/api/clients/folder/v1beta1';
import { Job, RepositoryView, useDeleteRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1';
@@ -61,6 +61,12 @@ function FormContent({ initialValues, parentFolder, repository, workflowOptions,
return;
}
+ reportInteraction('grafana_provisioning_folder_delete_submitted', {
+ workflow,
+ repositoryName: repo,
+ repositoryType: repository?.type ?? 'unknown',
+ });
+
// Branch workflow: use /files API for direct file operations
if (workflow === 'branch') {
const branchRef = ref;
diff --git a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx
index 10bcfcdcf66..3b9d62916a4 100644
--- a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx
+++ b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
-import { getAppEvents } from '@grafana/runtime';
+import { getAppEvents, reportInteraction } from '@grafana/runtime';
import { Alert, Button, Field, Input, Stack } from '@grafana/ui';
import { Folder } from 'app/api/clients/folder/v1beta1';
import { RepositoryView, useCreateRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1';
@@ -115,6 +115,12 @@ function FormContent({ initialValues, repository, workflowOptions, folder, onDis
ref = undefined;
}
+ reportInteraction('grafana_provisioning_folder_create_submitted', {
+ workflow,
+ repositoryName: repoName,
+ repositoryType: repository?.type ?? 'unknown',
+ });
+
create({
ref,
name: repoName,
diff --git a/public/app/features/provisioning/utils/data.ts b/public/app/features/provisioning/utils/data.ts
index 452a86d7e46..930e943d83b 100644
--- a/public/app/features/provisioning/utils/data.ts
+++ b/public/app/features/provisioning/utils/data.ts
@@ -2,7 +2,7 @@ import { RepositorySpec } from 'app/api/clients/provisioning/v0alpha1';
import { RepositoryFormData } from '../types';
-const getWorkflows = (data: RepositoryFormData): RepositorySpec['workflows'] => {
+export const getWorkflows = (data: RepositoryFormData): RepositorySpec['workflows'] => {
if (data.readOnly) {
return [];
}