1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-26 01:17:00 +02:00

Change request event tracking (#2570)

This commit is contained in:
Mateusz Kwasniewski 2022-11-30 12:04:29 +01:00 committed by GitHub
parent d1c565735a
commit fab6fbb756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 115 additions and 129 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

@ -1,4 +1,4 @@
import { Alert, Button, styled } from '@mui/material'; import { Alert, Button, styled, Typography } from '@mui/material';
import { FC, useContext, useState } from 'react'; import { FC, useContext, useState } from 'react';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest'; import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';

View File

@ -41,7 +41,7 @@ export const FeatureStrategyCreate = () => {
const errors = useFormErrors(); const errors = useFormErrors();
const { addStrategyToFeature, loading } = useFeatureStrategyApi(); const { addStrategyToFeature, loading } = useFeatureStrategyApi();
const { addChangeRequest } = useChangeRequestApi(); const { addChange } = useChangeRequestApi();
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const { unleashUrl } = uiConfig; const { unleashUrl } = uiConfig;
@ -98,7 +98,7 @@ export const FeatureStrategyCreate = () => {
}; };
const onStrategyRequestAdd = async (payload: IFeatureStrategyPayload) => { const onStrategyRequestAdd = async (payload: IFeatureStrategyPayload) => {
await addChangeRequest(projectId, environmentId, { await addChange(projectId, environmentId, {
action: 'addStrategy', action: 'addStrategy',
feature: featureId, feature: featureId,
payload, payload,

View File

@ -43,7 +43,7 @@ export const FeatureStrategyEdit = () => {
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
const { unleashUrl } = uiConfig; const { unleashUrl } = uiConfig;
const navigate = useNavigate(); const navigate = useNavigate();
const { addChangeRequest } = useChangeRequestApi(); const { addChange } = useChangeRequestApi();
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { refetch: refetchChangeRequests } = const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId); usePendingChangeRequests(projectId);
@ -110,7 +110,7 @@ export const FeatureStrategyEdit = () => {
}; };
const onStrategyRequestEdit = async (payload: IFeatureStrategyPayload) => { const onStrategyRequestEdit = async (payload: IFeatureStrategyPayload) => {
await addChangeRequest(projectId, environmentId, { await addChange(projectId, environmentId, {
action: 'updateStrategy', action: 'updateStrategy',
feature: featureId, feature: featureId,
payload: { ...payload, id: strategyId }, payload: { ...payload, id: strategyId },

View File

@ -130,14 +130,14 @@ const useOnSuggestRemove = ({
environmentId, environmentId,
strategyId, strategyId,
}: IRemoveProps) => { }: IRemoveProps) => {
const { addChangeRequest } = useChangeRequestApi(); const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } = const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId); usePendingChangeRequests(projectId);
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const onSuggestRemove = async (event: React.FormEvent) => { const onSuggestRemove = async (event: React.FormEvent) => {
try { try {
event.preventDefault(); event.preventDefault();
await addChangeRequest(projectId, environmentId, { await addChange(projectId, environmentId, {
action: 'deleteStrategy', action: 'deleteStrategy',
feature: featureId, feature: featureId,
payload: { payload: {

View File

@ -29,6 +29,7 @@ import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import { KeyboardArrowDownOutlined } from '@mui/icons-material'; import { KeyboardArrowDownOutlined } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
const StyledBox = styled(Box)(({ theme }) => ({ const StyledBox = styled(Box)(({ theme }) => ({
padding: theme.spacing(1), padding: theme.spacing(1),
@ -40,6 +41,7 @@ const StyledBox = styled(Box)(({ theme }) => ({
})); }));
export const ChangeRequestConfiguration: VFC = () => { export const ChangeRequestConfiguration: VFC = () => {
const { trackEvent } = usePlausibleTracker();
const [dialogState, setDialogState] = useState<{ const [dialogState, setDialogState] = useState<{
isOpen: boolean; isOpen: boolean;
enableEnvironment: string; enableEnvironment: string;
@ -256,7 +258,17 @@ export const ChangeRequestConfiguration: VFC = () => {
</Table> </Table>
<Dialogue <Dialogue
onClick={() => onConfirm()} onClick={() => {
trackEvent('change_request', {
props: {
eventType: `change request ${
!dialogState.isEnabled ? 'enabled' : 'disabled'
}`,
},
});
onConfirm();
}}
open={dialogState.isOpen} open={dialogState.isOpen}
onClose={() => onClose={() =>
setDialogState(state => ({ ...state, isOpen: false })) setDialogState(state => ({ ...state, isOpen: false }))

View File

@ -31,8 +31,12 @@ export const ChangeRequestProcessHelp: VFC<
</Typography> </Typography>
} }
/> />
<IconButton title="Change request process" ref={ref}> <IconButton
<HelpOutline onClick={() => setIsOpen(true)} /> title="Change request process"
ref={ref}
onClick={() => setIsOpen(true)}
>
<HelpOutline />
</IconButton> </IconButton>
<Popover <Popover
open={isOpen} open={isOpen}
@ -64,20 +68,20 @@ export const ChangeRequestProcessHelp: VFC<
<li> <li>
These changes can be seen by everyone but only These changes can be seen by everyone but only
who has <strong>Review change request</strong>{' '} who has <strong>Review change request</strong>{' '}
permission can Approve or Reject them permission can Approve them
</li> </li>
<ul> <ul>
<li> <li>
If changes are Approved then someone who has If changes are Approved then someone who has{' '}
<strong>Apply change request</strong>{' '} <strong>Apply change request</strong>{' '}
permission needs to apply these changes to permission needs to apply these changes to
be live on the feature toggles and request be live on the feature toggles and request
is Closed is Closed
</li> </li>
<li> <li>
If changes are Rejected then these goes If changes are Cancelled by the author or
automatically to Cancelled and request is admin then change request goes automatically
Closed. to Cancelled and request is Closed.
</li> </li>
</ul> </ul>
</ol> </ol>

View File

@ -1,6 +1,7 @@
import useAPI from '../useApi/useApi'; import useAPI from '../useApi/useApi';
import { usePlausibleTracker } from '../../../usePlausibleTracker';
export interface IChangeRequestsSchema { export interface IChangeSchema {
feature: string; feature: string;
action: action:
| 'updateEnabled' | 'updateEnabled'
@ -18,15 +19,22 @@ export interface IChangeRequestConfig {
} }
export const useChangeRequestApi = () => { export const useChangeRequestApi = () => {
const { trackEvent } = usePlausibleTracker();
const { makeRequest, createRequest, errors, loading } = useAPI({ const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true, propagateErrors: true,
}); });
const addChangeRequest = async ( const addChange = async (
project: string, project: string,
environment: string, environment: string,
payload: IChangeRequestsSchema payload: IChangeSchema
) => { ) => {
trackEvent('change_request', {
props: {
eventType: 'change added',
},
});
const path = `api/admin/projects/${project}/environments/${environment}/change-requests`; const path = `api/admin/projects/${project}/environments/${environment}/change-requests`;
const req = createRequest(path, { const req = createRequest(path, {
method: 'POST', method: 'POST',
@ -43,8 +51,14 @@ export const useChangeRequestApi = () => {
const changeState = async ( const changeState = async (
project: string, project: string,
changeRequestId: number, changeRequestId: number,
payload: any payload: { state: 'Approved' | 'Applied' | 'Cancelled' | 'In review' }
) => { ) => {
trackEvent('change_request', {
props: {
eventType: payload.state,
},
});
const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/state`; const path = `api/admin/projects/${project}/change-requests/${changeRequestId}/state`;
const req = createRequest(path, { const req = createRequest(path, {
method: 'PUT', method: 'PUT',
@ -114,6 +128,12 @@ export const useChangeRequestApi = () => {
changeRequestId: string, changeRequestId: string,
text: string text: string
) => { ) => {
trackEvent('change_request', {
props: {
eventType: 'comment added',
},
});
const path = `/api/admin/projects/${projectId}/change-requests/${changeRequestId}/comments`; const path = `/api/admin/projects/${projectId}/change-requests/${changeRequestId}/comments`;
const req = createRequest(path, { const req = createRequest(path, {
method: 'POST', method: 'POST',
@ -128,7 +148,7 @@ export const useChangeRequestApi = () => {
}; };
return { return {
addChangeRequest, addChange,
changeState, changeState,
discardChange, discardChange,
updateChangeRequestEnvironmentConfig, updateChangeRequestEnvironmentConfig,

View File

@ -16,7 +16,7 @@ export const useChangeRequestAddStrategy = (
action: ChangeRequestStrategyAction action: ChangeRequestStrategyAction
) => { ) => {
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { addChangeRequest } = useChangeRequestApi(); const { addChange } = useChangeRequestApi();
const { refetch } = usePendingChangeRequests(project); const { refetch } = usePendingChangeRequests(project);
const [changeRequestDialogDetails, setChangeRequestDialogDetails] = const [changeRequestDialogDetails, setChangeRequestDialogDetails] =
@ -69,15 +69,11 @@ export const useChangeRequestAddStrategy = (
const onChangeRequestAddStrategyConfirm = useCallback(async () => { const onChangeRequestAddStrategyConfirm = useCallback(async () => {
try { try {
await addChangeRequest( await addChange(project, changeRequestDialogDetails.environment!, {
project, feature: changeRequestDialogDetails.featureName!,
changeRequestDialogDetails.environment!, action: action,
{ payload: changeRequestDialogDetails.strategy!,
feature: changeRequestDialogDetails.featureName!, });
action: action,
payload: changeRequestDialogDetails.strategy!,
}
);
refetch(); refetch();
setChangeRequestDialogDetails({ isOpen: false }); setChangeRequestDialogDetails({ isOpen: false });
setToastData({ setToastData({
@ -88,13 +84,13 @@ export const useChangeRequestAddStrategy = (
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
setChangeRequestDialogDetails({ isOpen: false }); setChangeRequestDialogDetails({ isOpen: false });
} }
}, [addChangeRequest]); }, [addChange]);
const onChangeRequestAddStrategiesConfirm = useCallback(async () => { const onChangeRequestAddStrategiesConfirm = useCallback(async () => {
try { try {
await Promise.all( await Promise.all(
changeRequestDialogDetails.strategies!.map(strategy => { changeRequestDialogDetails.strategies!.map(strategy => {
return addChangeRequest( return addChange(
project, project,
changeRequestDialogDetails.environment!, changeRequestDialogDetails.environment!,
{ {
@ -115,7 +111,7 @@ export const useChangeRequestAddStrategy = (
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
setChangeRequestDialogDetails({ isOpen: false }); setChangeRequestDialogDetails({ isOpen: false });
} }
}, [addChangeRequest]); }, [addChange]);
return { return {
onChangeRequestAddStrategy, onChangeRequestAddStrategy,

View File

@ -6,7 +6,7 @@ import { usePendingChangeRequests } from './api/getters/usePendingChangeRequests
export const useChangeRequestToggle = (project: string) => { export const useChangeRequestToggle = (project: string) => {
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const { addChangeRequest } = useChangeRequestApi(); const { addChange } = useChangeRequestApi();
const { refetch: refetchChangeRequests } = const { refetch: refetchChangeRequests } =
usePendingChangeRequests(project); usePendingChangeRequests(project);
@ -36,17 +36,13 @@ export const useChangeRequestToggle = (project: string) => {
const onChangeRequestToggleConfirm = useCallback(async () => { const onChangeRequestToggleConfirm = useCallback(async () => {
try { try {
await addChangeRequest( await addChange(project, changeRequestDialogDetails.environment!, {
project, feature: changeRequestDialogDetails.featureName!,
changeRequestDialogDetails.environment!, action: 'updateEnabled',
{ payload: {
feature: changeRequestDialogDetails.featureName!, enabled: Boolean(changeRequestDialogDetails.enabled),
action: 'updateEnabled', },
payload: { });
enabled: Boolean(changeRequestDialogDetails.enabled),
},
}
);
refetchChangeRequests(); refetchChangeRequests();
setChangeRequestDialogDetails(prev => ({ ...prev, isOpen: false })); setChangeRequestDialogDetails(prev => ({ ...prev, isOpen: false }));
setToastData({ setToastData({
@ -57,7 +53,7 @@ export const useChangeRequestToggle = (project: string) => {
setToastApiError(formatUnknownError(error)); setToastApiError(formatUnknownError(error));
setChangeRequestDialogDetails(prev => ({ ...prev, isOpen: false })); setChangeRequestDialogDetails(prev => ({ ...prev, isOpen: false }));
} }
}, [addChangeRequest]); }, [addChange]);
return { return {
onChangeRequestToggle, onChangeRequestToggle,

View File

@ -8,7 +8,7 @@ import { EventOptions, PlausibleOptions } from 'plausible-tracker';
* @see https://plausible.io/docs/custom-event-goals#2-create-a-custom-event-goal-in-your-plausible-analytics-account * @see https://plausible.io/docs/custom-event-goals#2-create-a-custom-event-goal-in-your-plausible-analytics-account
* @example `'download | 'invite' | 'signup'` * @example `'download | 'invite' | 'signup'`
**/ **/
type CustomEvents = 'invite' | 'upgrade_plan_clicked'; type CustomEvents = 'invite' | 'upgrade_plan_clicked' | 'change_request';
export const usePlausibleTracker = () => { export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext); const plausible = useContext(PlausibleContext);