Provisioning: Add FE analytics (#112118)
* Provisioning: Add FE analytics * Cleanup * More cleanup * Add missing workflow * Add more tracking
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
</Button>
|
||||
)}
|
||||
<SyncRepository repository={repository} />
|
||||
<LinkButton variant="secondary" icon="cog" href={`${PROVISIONING_URL}/${name}/edit`}>
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
icon="cog"
|
||||
href={`${PROVISIONING_URL}/${name}/edit`}
|
||||
onClick={() => {
|
||||
reportInteraction('grafana_provisioning_repository_settings_opened', {
|
||||
repositoryName: name,
|
||||
repositoryType: repository.spec?.type ?? 'unknown',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="provisioning.repository-actions.settings">Settings</Trans>
|
||||
</LinkButton>
|
||||
</Stack>
|
||||
|
||||
@@ -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) {
|
||||
<Trans i18nKey="provisioning.repository-card.view">View</Trans>
|
||||
</LinkButton>
|
||||
<SyncRepository repository={repository} />
|
||||
<LinkButton variant="secondary" icon="cog" href={`${PROVISIONING_URL}/${name}/edit`} size="md">
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
icon="cog"
|
||||
href={`${PROVISIONING_URL}/${name}/edit`}
|
||||
size="md"
|
||||
onClick={() => {
|
||||
reportInteraction('grafana_provisioning_repository_settings_opened', {
|
||||
repositoryName: name,
|
||||
repositoryType: spec?.type ?? 'unknown',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="provisioning.repository-card.settings">Settings</Trans>
|
||||
</LinkButton>
|
||||
</Stack>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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}`);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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' });
|
||||
|
||||
+12
-1
@@ -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',
|
||||
|
||||
+8
-1
@@ -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',
|
||||
|
||||
+7
-1
@@ -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;
|
||||
|
||||
+7
-1
@@ -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) {
|
||||
|
||||
+16
-2
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user