Skip to content

Commit 2ca5ccd

Browse files
authored
feat: add tracking for secrets management events (#1491)
* feat: add tracking for secrets management events * chore: regenerate analytics events documentation * refactor: remove secret names from tracking and extract source type - Extract SecretsManagementSource type to avoid repetition - Remove secretName from all tracking events to only track high-level interactions - Update all tracking calls to remove secret details - Regenerate analytics events documentation * refactor: reduce repetition in secrets management tracking events - Consolidate 6 interfaces into 2 reusable interfaces - Use SecretsManagementEvent interface for events with only source property - Keep CreateSecretButtonClicked separate due to additional location property - Regenerate analytics events documentation
1 parent 54a223c commit 2ca5ccd

File tree

9 files changed

+170
-18
lines changed

9 files changed

+170
-18
lines changed

docs/analytics/analytics-events.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,69 @@ Tracks when an alert is deleted successfully
221221
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
222222
| name | `"ProbeFailedExecutionsTooHigh" \| "TLSTargetCertificateCloseToExpiring" \| "HTTPRequestDurationTooHighAvg" \| "PingRequestDurationTooHighAvg" \| "DNSRequestDurationTooHighAvg"` | The name of the alert |
223223

224+
### secrets_management
225+
226+
#### synthetic-monitoring_secrets_management_create_secret_button_clicked
227+
228+
Tracks when the create secret button is clicked.
229+
230+
##### Properties
231+
232+
| name | type | description |
233+
| -------- | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
234+
| source | `"check_editor_sidepanel_feature_tabs" \| "config_page_secrets_tab"` | The source context where the secrets management UI is being used. |
235+
| location | `"empty_state" \| "header_action"` | The location where the create button was clicked. |
236+
237+
#### synthetic-monitoring_secrets_management_edit_secret_button_clicked
238+
239+
Tracks when the edit secret button is clicked.
240+
241+
##### Properties
242+
243+
| name | type | description |
244+
| ------ | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
245+
| source | `"check_editor_sidepanel_feature_tabs" \| "config_page_secrets_tab"` | The source context where the secrets management UI is being used. |
246+
247+
#### synthetic-monitoring_secrets_management_delete_secret_button_clicked
248+
249+
Tracks when the delete secret button is clicked.
250+
251+
##### Properties
252+
253+
| name | type | description |
254+
| ------ | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
255+
| source | `"check_editor_sidepanel_feature_tabs" \| "config_page_secrets_tab"` | The source context where the secrets management UI is being used. |
256+
257+
#### synthetic-monitoring_secrets_management_secret_created
258+
259+
Tracks when a secret is successfully created.
260+
261+
##### Properties
262+
263+
| name | type | description |
264+
| ------ | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
265+
| source | `"check_editor_sidepanel_feature_tabs" \| "config_page_secrets_tab"` | The source context where the secrets management UI is being used. |
266+
267+
#### synthetic-monitoring_secrets_management_secret_updated
268+
269+
Tracks when a secret is successfully updated.
270+
271+
##### Properties
272+
273+
| name | type | description |
274+
| ------ | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
275+
| source | `"check_editor_sidepanel_feature_tabs" \| "config_page_secrets_tab"` | The source context where the secrets management UI is being used. |
276+
277+
#### synthetic-monitoring_secrets_management_secret_deleted
278+
279+
Tracks when a secret is successfully deleted.
280+
281+
##### Properties
282+
283+
| name | type | description |
284+
| ------ | -------------------------------------------------------------------- | ----------------------------------------------------------------- |
285+
| source | `"check_editor_sidepanel_feature_tabs" \| "config_page_secrets_tab"` | The source context where the secrets management UI is being used. |
286+
224287
### timepoint_explorer
225288

226289
#### synthetic-monitoring_timepoint_explorer_view_toggle

src/components/Checkster/feature/secrets/SecretsPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function SecretsPanel() {
1616
padding: ${theme.spacing(2)};
1717
`}
1818
>
19-
<SecretsManagementUI />
19+
<SecretsManagementUI source="check_editor_sidepanel_feature_tabs" />
2020
</div>
2121
);
2222
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { createSMEventFactory, TrackingEventProps } from 'features/tracking/utils';
2+
3+
import { SecretsManagementSource } from 'page/ConfigPageLayout/tabs/SecretsManagementTab/types';
4+
5+
const secretsManagementEvents = createSMEventFactory('secrets_management');
6+
7+
interface CreateSecretButtonClicked extends TrackingEventProps {
8+
/** The source context where the secrets management UI is being used. */
9+
source: SecretsManagementSource;
10+
/** The location where the create button was clicked. */
11+
location: 'empty_state' | 'header_action';
12+
}
13+
14+
/** Tracks when the create secret button is clicked. */
15+
export const trackCreateSecretButtonClicked =
16+
secretsManagementEvents<CreateSecretButtonClicked>('create_secret_button_clicked');
17+
18+
interface SecretsManagementEvent extends TrackingEventProps {
19+
/** The source context where the secrets management UI is being used. */
20+
source: SecretsManagementSource;
21+
}
22+
23+
/** Tracks when the edit secret button is clicked. */
24+
export const trackEditSecretButtonClicked =
25+
secretsManagementEvents<SecretsManagementEvent>('edit_secret_button_clicked');
26+
27+
/** Tracks when the delete secret button is clicked. */
28+
export const trackDeleteSecretButtonClicked =
29+
secretsManagementEvents<SecretsManagementEvent>('delete_secret_button_clicked');
30+
31+
/** Tracks when a secret is successfully created. */
32+
export const trackSecretCreated = secretsManagementEvents<SecretsManagementEvent>('secret_created');
33+
34+
/** Tracks when a secret is successfully updated. */
35+
export const trackSecretUpdated = secretsManagementEvents<SecretsManagementEvent>('secret_updated');
36+
37+
/** Tracks when a secret is successfully deleted. */
38+
export const trackSecretDeleted = secretsManagementEvents<SecretsManagementEvent>('secret_deleted');

src/page/ConfigPageLayout/tabs/SecretsManagementTab/SecretEditModal.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ describe('SecretEditModal', () => {
2424
name: SECRETS_EDIT_MODE_ADD,
2525
onDismiss: jest.fn(),
2626
open: true,
27-
};
27+
source: 'config_page_secrets_tab',
28+
} as const;
2829

2930
it('should not render when open is false', async () => {
3031
await render(<SecretEditModal {...defaultProps} open={false} />);

src/page/ConfigPageLayout/tabs/SecretsManagementTab/SecretEditModal.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { GrafanaTheme2 } from '@grafana/data';
44
import { Alert, Button, Field, IconButton, Input, Modal, TextLink, useStyles2 } from '@grafana/ui';
55
import { css } from '@emotion/css';
66
import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';
7+
import { trackSecretCreated, trackSecretUpdated } from 'features/tracking/secretsManagementEvents';
78

8-
import { Secret } from './types';
9+
import { Secret, SecretsManagementSource } from './types';
910
import { getErrorMessage } from 'utils';
1011
import { useSaveSecret, useSecret } from 'data/useSecrets';
1112

@@ -19,6 +20,8 @@ interface SecretEditModalProps {
1920
onDismiss: () => void;
2021
open?: boolean;
2122
existingNames?: string[];
23+
/** The source context where the secrets management UI is being used. */
24+
source: SecretsManagementSource;
2225
}
2326

2427
function getDefaultValues(isNew = true): SecretFormValues & { plaintext?: string } {
@@ -62,7 +65,7 @@ function createGetFieldError(errors: FormErrorMap) {
6265
};
6366
}
6467

65-
export function SecretEditModal({ open, name, onDismiss, existingNames = [] }: SecretEditModalProps) {
68+
export function SecretEditModal({ open, name, onDismiss, existingNames = [], source }: SecretEditModalProps) {
6669
const { data: secret, isLoading, isError: hasFetchError, error: fetchError } = useSecret(name);
6770
const saveSecret = useSaveSecret();
6871
const isNewSecret = name === SECRETS_EDIT_MODE_ADD;
@@ -123,6 +126,11 @@ export function SecretEditModal({ open, name, onDismiss, existingNames = [] }: S
123126
},
124127
onSuccess() {
125128
setSaveError(null);
129+
if (isNewSecret) {
130+
trackSecretCreated({ source });
131+
} else {
132+
trackSecretUpdated({ source });
133+
}
126134
onDismiss();
127135
},
128136
});

src/page/ConfigPageLayout/tabs/SecretsManagementTab/SecretsManagementTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function SecretsManagementTab() {
2020

2121
return (
2222
<QueryErrorBoundary>
23-
<SecretsManagementUI />
23+
<SecretsManagementUI source="config_page_secrets_tab" />
2424
</QueryErrorBoundary>
2525
);
2626
}

src/page/ConfigPageLayout/tabs/SecretsManagementTab/SecretsManagementUI.test.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
runTestAsSecretsReadOnly,
99
} from 'test/utils';
1010

11+
import { SecretsManagementSource } from './types';
1112
import { useDeleteSecret, useSecrets } from 'data/useSecrets';
1213

1314
import { MOCKED_SECRETS } from '../../../../test/fixtures/secrets';
@@ -16,7 +17,17 @@ import { SecretsManagementTab } from './SecretsManagementTab';
1617
import { SecretsManagementUI } from './SecretsManagementUI';
1718

1819
jest.mock('./SecretEditModal', () => ({
19-
SecretEditModal: ({ onDismiss, open, name }: { onDismiss?: () => void; name: string; open?: boolean }) => {
20+
SecretEditModal: ({
21+
onDismiss,
22+
open,
23+
name,
24+
source,
25+
}: {
26+
onDismiss?: () => void;
27+
name: string;
28+
open?: boolean;
29+
source: SecretsManagementSource;
30+
}) => {
2031
if (!open) {
2132
return null;
2233
}
@@ -70,14 +81,14 @@ describe('SecretsManagementUI', () => {
7081

7182
it('should render empty state when no secrets exist', async () => {
7283
runTestAsSecretsFullAccess();
73-
render(<SecretsManagementUI />);
84+
render(<SecretsManagementUI source="config_page_secrets_tab" />);
7485

7586
expect(screen.getByText(/you don't have any secrets yet/i)).toBeInTheDocument();
7687
});
7788

7889
it('should have create button for users with create permissions', async () => {
7990
runTestAsSecretsFullAccess();
80-
render(<SecretsManagementUI />);
91+
render(<SecretsManagementUI source="config_page_secrets_tab" />);
8192

8293
const addButton = screen.getByRole('button', { name: /create secret/i });
8394
await userEvent.click(addButton);
@@ -88,22 +99,22 @@ describe('SecretsManagementUI', () => {
8899

89100
it('should have create button for users with creator permissions', async () => {
90101
runTestAsSecretsCreator();
91-
render(<SecretsManagementUI />);
102+
render(<SecretsManagementUI source="config_page_secrets_tab" />);
92103

93104
expect(screen.getByRole('button', { name: /create secret/i })).toBeInTheDocument();
94105
});
95106

96107
it('should not have create button for read-only users', async () => {
97108
runTestAsSecretsReadOnly();
98-
render(<SecretsManagementUI />);
109+
render(<SecretsManagementUI source="config_page_secrets_tab" />);
99110

100111
expect(screen.queryByRole('button', { name: /create secret/i })).not.toBeInTheDocument();
101112
expect(screen.getByText(/Contact an admin to create secrets/)).toBeInTheDocument();
102113
});
103114

104115
it('should not have create button for editor-only users', async () => {
105116
runTestAsSecretsEditor();
106-
render(<SecretsManagementUI />);
117+
render(<SecretsManagementUI source="config_page_secrets_tab" />);
107118

108119
expect(screen.queryByRole('button', { name: /create secret/i })).not.toBeInTheDocument();
109120
expect(screen.getByText(/Contact an admin to create secrets/)).toBeInTheDocument();

src/page/ConfigPageLayout/tabs/SecretsManagementTab/SecretsManagementUI.tsx

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import React, { useState } from 'react';
22
import { Button, ConfirmModal, EmptyState } from '@grafana/ui';
33
import { css } from '@emotion/css';
4+
import {
5+
trackCreateSecretButtonClicked,
6+
trackDeleteSecretButtonClicked,
7+
trackEditSecretButtonClicked,
8+
trackSecretDeleted,
9+
} from 'features/tracking/secretsManagementEvents';
410

5-
import { SecretWithUuid } from './types';
11+
import { SecretsManagementSource, SecretWithUuid } from './types';
612
import { getUserPermissions } from 'data/permissions';
713
import { useDeleteSecret, useSecrets } from 'data/useSecrets';
814
import { CenteredSpinner } from 'components/CenteredSpinner';
@@ -12,7 +18,12 @@ import { SECRETS_EDIT_MODE_ADD } from './constants';
1218
import { SecretCard } from './SecretCard';
1319
import { SecretEditModal } from './SecretEditModal';
1420

15-
export function SecretsManagementUI() {
21+
interface SecretsManagementUIProps {
22+
/** The source context where the secrets management UI is being used. */
23+
source: SecretsManagementSource;
24+
}
25+
26+
export function SecretsManagementUI({ source }: SecretsManagementUIProps) {
1627
const [editMode, setEditMode] = useState<string | false>(false);
1728
const [deleteMode, setDeleteMode] = useState<SecretWithUuid | undefined>();
1829
const { canCreateSecrets, canReadSecrets } = getUserPermissions();
@@ -22,15 +33,20 @@ export function SecretsManagementUI() {
2233

2334
const existingNames = secrets?.map((secret) => secret.name) ?? [];
2435

25-
const handleAddSecret = () => {
36+
const handleAddSecret = (location: 'empty_state' | 'header_action') => {
37+
trackCreateSecretButtonClicked({ source, location });
2638
setEditMode(SECRETS_EDIT_MODE_ADD);
2739
};
2840

2941
const handleEditSecret = (name?: string) => {
42+
if (name) {
43+
trackEditSecretButtonClicked({ source });
44+
}
3045
setEditMode(name ?? false);
3146
};
3247

3348
const handleDeleteSecret = (name: string) => {
49+
trackDeleteSecretButtonClicked({ source });
3450
const secret = secrets?.find((s) => s.name === name);
3551
if (secret) {
3652
setDeleteMode(secret);
@@ -50,7 +66,7 @@ export function SecretsManagementUI() {
5066
message="You don't have any secrets yet."
5167
button={
5268
canCreateSecrets ? (
53-
<Button onClick={handleAddSecret} icon="plus">
69+
<Button onClick={() => handleAddSecret('empty_state')} icon="plus">
5470
Create secret
5571
</Button>
5672
) : undefined
@@ -70,7 +86,7 @@ export function SecretsManagementUI() {
7086
actions={
7187
canCreateSecrets ? (
7288
<div>
73-
<Button size="sm" icon="plus" onClick={handleAddSecret}>
89+
<Button size="sm" icon="plus" onClick={() => handleAddSecret('header_action')}>
7490
Create secret
7591
</Button>
7692
</div>
@@ -94,7 +110,13 @@ export function SecretsManagementUI() {
94110
)}
95111

96112
{editMode && (
97-
<SecretEditModal name={editMode} existingNames={existingNames} open onDismiss={() => handleEditSecret()} />
113+
<SecretEditModal
114+
name={editMode}
115+
existingNames={existingNames}
116+
open
117+
source={source}
118+
onDismiss={() => handleEditSecret()}
119+
/>
98120
)}
99121

100122
<ConfirmModal
@@ -114,7 +136,13 @@ export function SecretsManagementUI() {
114136
</div>
115137
}
116138
onConfirm={() => {
117-
deleteMode && deleteSecret.mutate(deleteMode.name);
139+
if (deleteMode) {
140+
deleteSecret.mutate(deleteMode.name, {
141+
onSuccess: () => {
142+
trackSecretDeleted({ source });
143+
},
144+
});
145+
}
118146
setDeleteMode(undefined);
119147
}}
120148
onDismiss={() => setDeleteMode(undefined)}

src/page/ConfigPageLayout/tabs/SecretsManagementTab/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ export interface SecretWithUuid extends Omit<Secret, 'uuid'> {
2929
}
3030

3131
export interface SecretWithMetadata extends SecretWithUuid, SecretMetadata {}
32+
33+
/** The source context where the secrets management UI is being used. */
34+
export type SecretsManagementSource = 'check_editor_sidepanel_feature_tabs' | 'config_page_secrets_tab';

0 commit comments

Comments
 (0)