feat(telemetry): add telemetry events to the settings page (#6198)

This commit is contained in:
Aditya Radhakrishnan 2022-10-13 11:41:47 -07:00 committed by GitHub
parent 09616ee2b3
commit a58502c5dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 220 additions and 22 deletions

View File

@ -27,6 +27,20 @@ export enum EventType {
DownloadAsCsvEvent,
SignUpEvent,
ResetCredentialsEvent,
CreateAccessTokenEvent,
RevokeAccessTokenEvent,
CreateGroupEvent,
CreateInviteLinkEvent,
CreateResetCredentialsLinkEvent,
DeleteEntityEvent,
SelectUserRoleEvent,
BatchSelectUserRoleEvent,
CreatePolicyEvent,
UpdatePolicyEvent,
DeactivatePolicyEvent,
ActivatePolicyEvent,
ShowSimplifiedHomepageEvent,
ShowStandardHomepageEvent,
}
/**
@ -247,6 +261,74 @@ export interface DownloadAsCsvEvent extends BaseEvent {
path: string;
}
export interface CreateAccessTokenEvent extends BaseEvent {
type: EventType.CreateAccessTokenEvent;
accessTokenType: string;
duration: string;
}
export interface RevokeAccessTokenEvent extends BaseEvent {
type: EventType.RevokeAccessTokenEvent;
}
export interface CreateGroupEvent extends BaseEvent {
type: EventType.CreateGroupEvent;
}
export interface CreateInviteLinkEvent extends BaseEvent {
type: EventType.CreateInviteLinkEvent;
roleUrn?: string;
}
export interface CreateResetCredentialsLinkEvent extends BaseEvent {
type: EventType.CreateResetCredentialsLinkEvent;
userUrn: string;
}
export interface DeleteEntityEvent extends BaseEvent {
type: EventType.DeleteEntityEvent;
entityUrn: string;
entityType: EntityType;
}
export interface SelectUserRoleEvent extends BaseEvent {
type: EventType.SelectUserRoleEvent;
roleUrn: string;
userUrn: string;
}
export interface BatchSelectUserRoleEvent extends BaseEvent {
type: EventType.BatchSelectUserRoleEvent;
roleUrn: string;
userUrns: string[];
}
export interface CreatePolicyEvent extends BaseEvent {
type: EventType.CreatePolicyEvent;
}
export interface UpdatePolicyEvent extends BaseEvent {
type: EventType.UpdatePolicyEvent;
policyUrn: string;
}
export interface DeactivatePolicyEvent extends BaseEvent {
type: EventType.DeactivatePolicyEvent;
policyUrn: string;
}
export interface ActivatePolicyEvent extends BaseEvent {
type: EventType.ActivatePolicyEvent;
policyUrn: string;
}
export interface ShowSimplifiedHomepageEvent extends BaseEvent {
type: EventType.ShowSimplifiedHomepageEvent;
}
export interface ShowStandardHomepageEvent extends BaseEvent {
type: EventType.ShowStandardHomepageEvent;
}
/**
* Event consisting of a union of specific event types.
*/
@ -272,4 +354,18 @@ export type Event =
| DownloadAsCsvEvent
| RecommendationClickEvent
| HomePageRecommendationClickEvent
| BatchEntityActionEvent;
| BatchEntityActionEvent
| CreateAccessTokenEvent
| RevokeAccessTokenEvent
| CreateGroupEvent
| CreateInviteLinkEvent
| CreateResetCredentialsLinkEvent
| DeleteEntityEvent
| SelectUserRoleEvent
| BatchSelectUserRoleEvent
| CreatePolicyEvent
| UpdatePolicyEvent
| DeactivatePolicyEvent
| ActivatePolicyEvent
| ShowSimplifiedHomepageEvent
| ShowStandardHomepageEvent;

View File

@ -3,6 +3,7 @@ import { message, Modal } from 'antd';
import { EntityType } from '../../../../types.generated';
import { useEntityRegistry } from '../../../useEntityRegistry';
import { getDeleteEntityMutation } from '../../../shared/deleteUtils';
import analytics, { EventType } from '../../../analytics';
/**
* Performs the flow for deleting an entity of a given type.
@ -25,6 +26,11 @@ function useDeleteEntity(urn: string, type: EntityType, entityData: any, onDelet
},
})
.then(() => {
analytics.event({
type: EventType.DeleteEntityEvent,
entityUrn: urn,
entityType: type,
});
message.loading({
content: 'Deleting...',
duration: 2,

View File

@ -3,6 +3,7 @@ import { message, Button, Input, Modal, Typography, Form, Collapse } from 'antd'
import { useCreateGroupMutation } from '../../../graphql/group.generated';
import { useEnterKeyListener } from '../../shared/useEnterKeyListener';
import { groupIdTextValidation } from '../../shared/textUtil';
import analytics, { EventType } from '../../analytics';
type Props = {
onClose: () => void;
@ -27,6 +28,13 @@ export default function CreateGroupModal({ onClose, onCreate }: Props) {
},
},
})
.then(({ errors }) => {
if (!errors) {
analytics.event({
type: EventType.CreateGroupEvent,
});
}
})
.catch((e) => {
message.destroy();
message.error({ content: `Failed to create group!: \n ${e.message || ''}`, duration: 3 });

View File

@ -2,6 +2,7 @@ import React from 'react';
import { message, Popconfirm } from 'antd';
import { useBatchAssignRoleMutation } from '../../../graphql/mutations.generated';
import { DataHubRole } from '../../../types.generated';
import analytics, { EventType } from '../../analytics';
type Props = {
visible: boolean;
@ -34,6 +35,11 @@ export default function AssignRoleConfirmation({
})
.then(({ errors }) => {
if (!errors) {
analytics.event({
type: EventType.SelectUserRoleEvent,
roleUrn: roleToAssign.urn,
userUrn,
});
message.success({
content: `Assigned role ${roleToAssign?.name} to user ${username}!`,
duration: 2,

View File

@ -10,6 +10,7 @@ import { DataHubRole } from '../../../types.generated';
import { mapRoleIcon } from './UserUtils';
import { useCreateInviteTokenMutation } from '../../../graphql/mutations.generated';
import { ANTD_GRAY } from '../../entity/shared/constants';
import analytics, { EventType } from '../../analytics';
const ModalSection = styled.div`
display: flex;
@ -138,6 +139,10 @@ export default function ViewInviteTokenModal({ visible, onClose }: Props) {
})
.then(({ data, errors }) => {
if (!errors) {
analytics.event({
type: EventType.CreateInviteLinkEvent,
roleUrn,
});
setInviteToken(data?.createInviteToken?.inviteToken || '');
message.success('Generated new invite link');
}

View File

@ -1,9 +1,10 @@
import { RedoOutlined } from '@ant-design/icons';
import { Button, Modal, Typography } from 'antd';
import { Button, message, Modal, Typography } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import { PageRoutes } from '../../../conf/Global';
import { useCreateNativeUserResetTokenMutation } from '../../../graphql/user.generated';
import analytics, { EventType } from '../../analytics';
const ModalSection = styled.div`
display: flex;
@ -43,9 +44,36 @@ export default function ViewResetTokenModal({ visible, userUrn, username, onClos
const baseUrl = window.location.origin;
const [hasGeneratedResetToken, setHasGeneratedResetToken] = useState(false);
const [createNativeUserResetToken, { data: createNativeUserResetTokenData }] =
const [createNativeUserResetTokenMutation, { data: createNativeUserResetTokenData }] =
useCreateNativeUserResetTokenMutation({});
const createNativeUserResetToken = () => {
createNativeUserResetTokenMutation({
variables: {
input: {
userUrn,
},
},
})
.then(({ errors }) => {
if (!errors) {
analytics.event({
type: EventType.CreateResetCredentialsLinkEvent,
userUrn,
});
setHasGeneratedResetToken(true);
message.success('Generated new link to reset credentials');
}
})
.catch((e) => {
message.destroy();
message.error({
content: `Failed to create new link to reset credentials : \n ${e.message || ''}`,
duration: 3,
});
});
};
const resetToken = createNativeUserResetTokenData?.createNativeUserResetToken?.resetToken || '';
const inviteLink = `${baseUrl}${PageRoutes.RESET_CREDENTIALS}?reset_token=${resetToken}`;
@ -86,20 +114,7 @@ export default function ViewResetTokenModal({ visible, userUrn, username, onClos
<ModalSectionParagraph>
Generate a new reset link! Note, any old links will <b>cease to be active</b>.
</ModalSectionParagraph>
<CreateResetTokenButton
onClick={() => {
createNativeUserResetToken({
variables: {
input: {
userUrn,
},
},
});
setHasGeneratedResetToken(true);
}}
size="small"
type="text"
>
<CreateResetTokenButton onClick={createNativeUserResetToken} size="small" type="text">
<RedoOutlined style={{}} />
</CreateResetTokenButton>
</ModalSection>

View File

@ -15,6 +15,7 @@ import {
PolicyMatchFilter,
PolicyMatchFilterInput,
PolicyMatchCriterionInput,
EntityType,
} from '../../../types.generated';
import { useAppConfig } from '../../useAppConfig';
import PolicyDetailsModal from './PolicyDetailsModal';
@ -33,6 +34,7 @@ import { useEntityRegistry } from '../../useEntityRegistry';
import { ANTD_GRAY } from '../../entity/shared/constants';
import { SearchBar } from '../../search/SearchBar';
import { scrollToTop } from '../../shared/searchUtils';
import analytics, { EventType } from '../../analytics';
const SourceContainer = styled.div``;
@ -247,6 +249,11 @@ export const ManagePolicies = () => {
content: `Are you sure you want to remove policy?`,
onOk() {
deletePolicy({ variables: { urn: policy?.urn as string } }); // There must be a focus policy urn.
analytics.event({
type: EventType.DeleteEntityEvent,
entityUrn: policy?.urn,
entityType: EntityType.DatahubPolicy,
});
message.success('Successfully removed policy.');
setTimeout(function () {
policiesRefetch();
@ -285,9 +292,16 @@ export const ManagePolicies = () => {
if (focusPolicyUrn) {
// If there's an URN associated with the focused policy, then we are editing an existing policy.
updatePolicy({ variables: { urn: focusPolicyUrn, input: toPolicyInput(savePolicy) } });
analytics.event({
type: EventType.UpdatePolicyEvent,
policyUrn: focusPolicyUrn,
});
} else {
// If there's no URN associated with the focused policy, then we are creating.
createPolicy({ variables: { input: toPolicyInput(savePolicy) } });
analytics.event({
type: EventType.CreatePolicyEvent,
});
}
message.success('Successfully saved policy.');
setTimeout(function () {
@ -369,7 +383,13 @@ export const ManagePolicies = () => {
{record?.state === PolicyState.Active ? (
<Button
disabled={!record?.editable}
onClick={() => onToggleActiveDuplicate(record?.policy)}
onClick={() => {
onToggleActiveDuplicate(record?.policy);
analytics.event({
type: EventType.DeactivatePolicyEvent,
policyUrn: record?.policy?.urn,
});
}}
style={{ color: record?.editable ? 'red' : ANTD_GRAY[6], width: 100 }}
>
DEACTIVATE
@ -377,7 +397,13 @@ export const ManagePolicies = () => {
) : (
<Button
disabled={!record?.editable}
onClick={() => onToggleActiveDuplicate(record?.policy)}
onClick={() => {
onToggleActiveDuplicate(record?.policy);
analytics.event({
type: EventType.ActivatePolicyEvent,
policyUrn: record?.policy?.urn,
});
}}
style={{ color: record?.editable ? 'green' : ANTD_GRAY[6], width: 100 }}
>
ACTIVATE

View File

@ -15,6 +15,7 @@ import { EntityCapabilityType } from '../../entity/Entity';
import { useBatchAssignRoleMutation } from '../../../graphql/mutations.generated';
import { CorpUser, DataHubRole, DataHubPolicy } from '../../../types.generated';
import RoleDetailsModal from './RoleDetailsModal';
import analytics, { EventType } from '../../analytics';
const SourceContainer = styled.div``;
@ -99,6 +100,11 @@ export const ManageRoles = () => {
})
.then(({ errors }) => {
if (!errors) {
analytics.event({
type: EventType.BatchSelectUserRoleEvent,
roleUrn: focusRole?.urn,
userUrns: actorUrns,
});
message.success({
content: `Assigned Role to users!`,
duration: 2,

View File

@ -14,6 +14,7 @@ import CreateTokenModal from './CreateTokenModal';
import { useAppConfigQuery } from '../../graphql/app.generated';
import { getLocaleTimezone } from '../shared/time/timeUtils';
import { scrollToTop } from '../shared/searchUtils';
import analytics, { EventType } from '../analytics';
const SourceContainer = styled.div`
width: 100%;
@ -130,7 +131,13 @@ export const AccessTokens = () => {
// Hack to deal with eventual consistency.
const newTokenIds = [...removedTokens, token.id];
setRemovedTokens(newTokenIds);
revokeAccessToken({ variables: { tokenId: token.id } })
.then(({ errors }) => {
if (!errors) {
analytics.event({ type: EventType.RevokeAccessTokenEvent });
}
})
.catch((e) => {
message.destroy();
message.error({ content: `Failed to revoke Token!: \n ${e.message || ''}`, duration: 3 });

View File

@ -8,6 +8,7 @@ import { ACCESS_TOKEN_DURATIONS, getTokenExpireDate } from './utils';
import { useCreateAccessTokenMutation } from '../../graphql/auth.generated';
import { AccessTokenDuration, AccessTokenType, CreateAccessTokenInput } from '../../types.generated';
import { AccessTokenModal } from './AccessTokenModal';
import analytics, { EventType } from '../analytics';
type Props = {
currentUserUrn: string;
@ -75,6 +76,15 @@ export default function CreateTokenModal({ currentUserUrn, visible, onClose, onC
description: tokenDescription,
};
createAccessToken({ variables: { input } })
.then(({ errors }) => {
if (!errors) {
analytics.event({
type: EventType.CreateAccessTokenEvent,
accessTokenType: AccessTokenType.Personal,
duration: selectedTokenDuration,
});
}
})
.catch((e) => {
message.destroy();
message.error({ content: `Failed to create Token!: \n ${e.message || ''}`, duration: 3 });

View File

@ -5,6 +5,7 @@ import { Divider, Typography, Switch, Card, message } from 'antd';
import { useGetMeQuery, useUpdateUserSettingMutation } from '../../graphql/me.generated';
import { UserSetting } from '../../types.generated';
import { ANTD_GRAY } from '../entity/shared/constants';
import analytics, { EventType } from '../analytics';
const Page = styled.div`
width: 100%;
@ -69,7 +70,7 @@ export const Preferences = () => {
<Card>
<UserSettingRow>
<span>
<SettingText>Show simplified hompeage </SettingText>
<SettingText>Show simplified homepage </SettingText>
<div>
<DescriptionText>
Limits entity browse cards on homepage to Domains, Charts, Datasets, Dashboards and
@ -88,6 +89,11 @@ export const Preferences = () => {
},
},
});
analytics.event({
type: showSimplifiedHomepage
? EventType.ShowStandardHomepageEvent
: EventType.ShowSimplifiedHomepageEvent,
});
message.success({ content: 'Setting updated!', duration: 2 });
refetch?.();
}}

View File

@ -49,11 +49,16 @@ public class TrackingService {
private static final String RENDER_TYPE_FIELD = "renderType";
private static final String SCENARIO_TYPE_FIELD = "scenarioType";
private static final String SECTION_FIELD = "section";
private static final String ACCESS_TOKEN_TYPE_FIELD = "accessTokenType";
private static final String DURATION_FIELD = "duration";
private static final String ROLE_URN_FIELD = "roleUrn";
private static final String POLICY_URN_FIELD = "policyUrn";
private static final Set<String> ALLOWED_EVENT_FIELDS = new HashSet<>(
ImmutableList.of(EVENT_TYPE_FIELD, SIGN_UP_TITLE_FIELD, ENTITY_TYPE_FIELD, ENTITY_TYPE_FILTER_FIELD,
PAGE_NUMBER_FIELD, PAGE_FIELD, TOTAL_FIELD, INDEX_FIELD, RESULT_TYPE_FIELD, RENDER_ID_FIELD, MODULE_ID_FIELD,
RENDER_TYPE_FIELD, SCENARIO_TYPE_FIELD, SECTION_FIELD));
RENDER_TYPE_FIELD, SCENARIO_TYPE_FIELD, SECTION_FIELD, ACCESS_TOKEN_TYPE_FIELD, DURATION_FIELD,
ROLE_URN_FIELD, POLICY_URN_FIELD));
private static final String ACTOR_URN_FIELD = "actorUrn";
private static final String ORIGIN_FIELD = "origin";
@ -62,9 +67,11 @@ public class TrackingService {
private static final String GROUP_NAME_FIELD = "groupName";
private static final String ENTITY_PAGE_FILTER_FIELD = "entityPageFilter";
private static final String PATH_FIELD = "path";
private static final String USER_URN_FIELD = "userUrn";
private static final String USER_URNS_FIELD = "userUrns";
private static final Set<String> ALLOWED_OBFUSCATED_EVENT_FIELDS = new HashSet<>(
ImmutableList.of(ACTOR_URN_FIELD, ORIGIN_FIELD, ENTITY_URN_FIELD, ENTITY_URNS_FIELD, GROUP_NAME_FIELD,
SECTION_FIELD, ENTITY_PAGE_FILTER_FIELD, PATH_FIELD));
SECTION_FIELD, ENTITY_PAGE_FILTER_FIELD, PATH_FIELD, USER_URN_FIELD, USER_URNS_FIELD));
private final MixpanelAPI _mixpanelAPI;
private final MessageBuilder _mixpanelMessageBuilder;