Merge pull request #11543 from strapi/v4/tracking

[V4] Tracking
This commit is contained in:
cyril lopez 2021-11-15 14:06:38 +01:00 committed by GitHub
commit b356766f1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 187 additions and 85 deletions

View File

@ -51,7 +51,7 @@ const TableRows = ({
key={data.id}
{...onRowClick({
fn: () => {
trackUsage('willEditEntryFromButton');
trackUsage('willEditEntryFromList');
push({
pathname: `${pathname}/${data.id}`,
state: { from: pathname },
@ -141,7 +141,11 @@ const TableRows = ({
{canDelete && (
<Box paddingLeft={1}>
<IconButton
onClick={() => onClickDelete(data.id)}
onClick={() => {
trackUsage('willDeleteEntryFromList');
onClickDelete(data.id);
}}
label={formatMessage(
{ id: 'app.component.table.delete', defaultMessage: 'Delete {target}' },
{ target: itemLineText }

View File

@ -81,6 +81,7 @@ const DynamicTable = ({
headers={tableHeaders}
onConfirmDelete={onConfirmDelete}
onConfirmDeleteAll={onConfirmDeleteAll}
onOpenDeleteAllModalTrackedEvent="willBulkDeleteEntries"
rows={rows}
withBulkActions
withMainAction={canDelete && isBulkable}

View File

@ -26,6 +26,8 @@ const FieldPicker = ({ layout }) => {
const values = displayedHeaders.map(({ name }) => name);
const handleChange = updatedValues => {
trackUsage('didChangeDisplayedFields');
// removing a header
if (updatedValues.length < values.length) {
const removedHeader = values.filter(value => {
@ -34,7 +36,6 @@ const FieldPicker = ({ layout }) => {
dispatch(onChangeListHeaders({ name: removedHeader[0], value: true }));
} else {
trackUsage('didChangeDisplayedFields');
const addedHeader = updatedValues.filter(value => {
return values.indexOf(value) === -1;
});

View File

@ -7,7 +7,6 @@ import { bindActionCreators, compose } from 'redux';
import { useIntl } from 'react-intl';
import { useHistory, useLocation } from 'react-router-dom';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import { stringify } from 'qs';
import {
NoPermissions,
@ -171,18 +170,6 @@ function ListView({
const handleConfirmDeleteData = useCallback(
async idToDelete => {
try {
let trackerProperty = {};
if (hasDraftAndPublish) {
const dataToDelete = data.find(obj => obj.id.toString() === idToDelete.toString());
const isDraftEntry = isEmpty(dataToDelete.publishedAt);
const status = isDraftEntry ? 'draft' : 'published';
trackerProperty = { status };
}
trackUsageRef.current('willDeleteEntry', trackerProperty);
await axiosInstance.delete(getRequestUrl(`collection-types/${slug}/${idToDelete}`));
const requestUrl = getRequestUrl(`collection-types/${slug}${params}`);
@ -192,8 +179,6 @@ function ListView({
type: 'success',
message: { id: getTrad('success.record.delete') },
});
trackUsageRef.current('didDeleteEntry', trackerProperty);
} catch (err) {
const errorMessage = get(
err,
@ -207,7 +192,7 @@ function ListView({
});
}
},
[hasDraftAndPublish, slug, params, fetchData, toggleNotification, data, formatMessage]
[slug, params, fetchData, toggleNotification, formatMessage]
);
useEffect(() => {
@ -282,8 +267,11 @@ function ListView({
<FieldPicker layout={layout} />
<CheckPermissions permissions={cmPermissions.collectionTypesConfigurations}>
<IconButtonCustom
onClick={() =>
push({ pathname: `${slug}/configurations/list`, search: pluginsQueryParams })}
onClick={() => {
trackUsage('willEditListLayout');
push({ pathname: `${slug}/configurations/list`, search: pluginsQueryParams });
}}
icon={<Cog />}
label={formatMessage({
id: 'app.links.configure-view',

View File

@ -1,8 +1,8 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import styled from 'styled-components';
import { Helmet } from 'react-helmet';
import { pxToRem, CheckPagePermissions } from '@strapi/helper-plugin';
import { pxToRem, CheckPagePermissions, useTracking } from '@strapi/helper-plugin';
import { Layout, HeaderLayout, ContentLayout } from '@strapi/design-system/Layout';
import { Flex } from '@strapi/design-system/Flex';
import { Box } from '@strapi/design-system/Box';
@ -27,6 +27,11 @@ const StackCentered = styled(Stack)`
const MarketPlacePage = () => {
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
useEffect(() => {
trackUsage('didGoToMarketplace');
}, [trackUsage]);
return (
<CheckPagePermissions permissions={adminPermissions.marketplace.main}>

View File

@ -2,11 +2,13 @@ import React from 'react';
import { render } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import { useTracking } from '@strapi/helper-plugin';
import MarketPlacePage from '../index';
jest.mock('@strapi/helper-plugin', () => ({
pxToRem: jest.fn(),
CheckPagePermissions: ({ children }) => children,
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const App = (
@ -440,4 +442,12 @@ describe('Marketplace coming soon', () => {
</div>
`);
});
it('sends an event when the user enters the marketplace', () => {
const trackUsage = jest.fn();
useTracking.mockImplementation(() => ({ trackUsage }));
render(App);
expect(trackUsage).toHaveBeenCalledWith('didGoToMarketplace');
});
});

View File

@ -9,6 +9,7 @@ import { useIntl } from 'react-intl';
import Trash from '@strapi/icons/Trash';
import styled from 'styled-components';
import useQueryParams from '../../hooks/useQueryParams';
import useTracking from '../../hooks/useTracking';
import ConfirmDialog from '../ConfirmDialog';
import EmptyBodyTable from '../EmptyBodyTable';
import TableHead from './TableHead';
@ -29,6 +30,7 @@ const Table = ({
isLoading,
onConfirmDeleteAll,
onConfirmDelete,
onOpenDeleteAllModalTrackedEvent,
rows,
withBulkActions,
withMainAction,
@ -40,6 +42,7 @@ const Table = ({
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
const [{ query }] = useQueryParams();
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const ROW_COUNT = rows.length + 1;
const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
const hasFilters = query?.filters !== undefined;
@ -88,6 +91,10 @@ const Table = ({
};
const handleToggleConfirmDeleteAll = () => {
if (!showConfirmDeleteAll && onOpenDeleteAllModalTrackedEvent) {
trackUsage(onOpenDeleteAllModalTrackedEvent);
}
setShowConfirmDeleteAll(prev => !prev);
};
@ -203,6 +210,7 @@ Table.defaultProps = {
isLoading: false,
onConfirmDeleteAll: () => {},
onConfirmDelete: () => {},
onOpenDeleteAllModalTrackedEvent: undefined,
rows: [],
withBulkActions: false,
withMainAction: false,
@ -229,6 +237,7 @@ Table.propTypes = {
isLoading: PropTypes.bool,
onConfirmDeleteAll: PropTypes.func,
onConfirmDelete: PropTypes.func,
onOpenDeleteAllModalTrackedEvent: PropTypes.string,
rows: PropTypes.array,
withBulkActions: PropTypes.bool,
withMainAction: PropTypes.bool,

View File

@ -29,6 +29,7 @@ export const AssetDialog = ({
onValidate,
multiple,
initiallySelectedAssets,
trackedLocation,
}) => {
const [assetToEdit, setAssetToEdit] = useState(undefined);
const { formatMessage } = useIntl();
@ -153,6 +154,7 @@ export const AssetDialog = ({
canUpdate={canUpdate}
canCopyLink={canCopyLink}
canDownload={canDownload}
trackedLocation={trackedLocation}
/>
);
}
@ -232,6 +234,7 @@ AssetDialog.defaultProps = {
allowedTypes: [],
initiallySelectedAssets: [],
multiple: false,
trackedLocation: undefined,
};
AssetDialog.propTypes = {
@ -241,4 +244,5 @@ AssetDialog.propTypes = {
onAddAsset: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onValidate: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -1,16 +1,23 @@
import PropTypes from 'prop-types';
import React, { useRef } from 'react';
import { useIntl } from 'react-intl';
import { useTracking } from '@strapi/helper-plugin';
import { Button } from '@strapi/design-system/Button';
import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
import { getTrad } from '../../utils';
export const ReplaceMediaButton = ({ onSelectMedia, acceptedMime, ...props }) => {
export const ReplaceMediaButton = ({ onSelectMedia, acceptedMime, trackedLocation, ...props }) => {
const { formatMessage } = useIntl();
const inputRef = useRef(null);
const { trackUsage } = useTracking();
const handleClick = e => {
e.preventDefault();
if (trackedLocation) {
trackUsage('didReplaceMedia', { location: trackedLocation });
}
inputRef.current.click();
};
@ -43,7 +50,12 @@ export const ReplaceMediaButton = ({ onSelectMedia, acceptedMime, ...props }) =>
);
};
ReplaceMediaButton.defaultProps = {
trackedLocation: undefined,
};
ReplaceMediaButton.propTypes = {
acceptedMime: PropTypes.string.isRequired,
onSelectMedia: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -36,7 +36,14 @@ const fileInfoSchema = yup.object({
caption: yup.string(),
});
export const EditAssetDialog = ({ onClose, asset, canUpdate, canCopyLink, canDownload }) => {
export const EditAssetDialog = ({
onClose,
asset,
canUpdate,
canCopyLink,
canDownload,
trackedLocation,
}) => {
const { formatMessage, formatDate } = useIntl();
const submitButtonRef = useRef(null);
const [isCropping, setIsCropping] = useState(false);
@ -183,6 +190,7 @@ export const EditAssetDialog = ({ onClose, asset, canUpdate, canCopyLink, canDow
onSelectMedia={setReplacementFile}
acceptedMime={asset.mime}
disabled={formDisabled}
trackedLocation={trackedLocation}
/>
<Button
@ -200,10 +208,15 @@ export const EditAssetDialog = ({ onClose, asset, canUpdate, canCopyLink, canDow
);
};
EditAssetDialog.defaultProps = {
trackedLocation: undefined,
};
EditAssetDialog.propTypes = {
asset: AssetDefinition.isRequired,
canUpdate: PropTypes.bool.isRequired,
canCopyLink: PropTypes.bool.isRequired,
canDownload: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -23,6 +23,7 @@ export const CarouselAssets = ({
onNext,
onPrevious,
selectedAssetIndex,
trackedLocation,
}) => {
const { formatMessage } = useIntl();
const [isEditingAsset, setIsEditingAsset] = useState(false);
@ -105,6 +106,7 @@ export const CarouselAssets = ({
canUpdate
canCopyLink
canDownload
trackedLocation={trackedLocation}
/>
)}
</>
@ -116,6 +118,7 @@ CarouselAssets.defaultProps = {
error: undefined,
hint: undefined,
onDropAsset: undefined,
trackedLocation: undefined,
};
CarouselAssets.propTypes = {
@ -132,4 +135,5 @@ CarouselAssets.propTypes = {
onNext: PropTypes.func.isRequired,
onPrevious: PropTypes.func.isRequired,
selectedAssetIndex: PropTypes.number.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -150,6 +150,7 @@ export const MediaLibraryInput = ({
error={errorMessage}
hint={hint}
selectedAssetIndex={selectedIndex}
trackedLocation="content-manager"
/>
{step === Steps.SelectAsset && (
@ -160,6 +161,7 @@ export const MediaLibraryInput = ({
onValidate={handleValidation}
multiple={multiple}
onAddAsset={() => setStep(Steps.UploadAsset)}
trackedLocation="content-manager"
/>
)}
@ -168,6 +170,7 @@ export const MediaLibraryInput = ({
onClose={() => setStep(Steps.SelectAsset)}
initialAssetsToAdd={droppedAssets}
addUploadedFiles={handleFilesUploadSucceeded}
trackedLocation="content-manager"
/>
)}
</>

View File

@ -10,7 +10,7 @@ import { FromUrlForm } from './FromUrlForm';
import { FromComputerForm } from './FromComputerForm';
import getTrad from '../../../utils/getTrad';
export const AddAssetStep = ({ onClose, onAddAsset }) => {
export const AddAssetStep = ({ onClose, onAddAsset, trackedLocation }) => {
const { formatMessage } = useIntl();
return (
@ -51,10 +51,18 @@ export const AddAssetStep = ({ onClose, onAddAsset }) => {
</Box>
<TabPanels>
<TabPanel>
<FromComputerForm onClose={onClose} onAddAssets={onAddAsset} />
<FromComputerForm
onClose={onClose}
onAddAssets={onAddAsset}
trackedLocation={trackedLocation}
/>
</TabPanel>
<TabPanel>
<FromUrlForm onClose={onClose} onAddAsset={onAddAsset} />
<FromUrlForm
onClose={onClose}
onAddAsset={onAddAsset}
trackedLocation={trackedLocation}
/>
</TabPanel>
</TabPanels>
</TabGroup>
@ -62,7 +70,12 @@ export const AddAssetStep = ({ onClose, onAddAsset }) => {
);
};
AddAssetStep.defaultProps = {
trackedLocation: undefined,
};
AddAssetStep.propTypes = {
onClose: PropTypes.func.isRequired,
onAddAsset: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -5,6 +5,7 @@ import styled from 'styled-components';
import { Box } from '@strapi/design-system/Box';
import { Flex } from '@strapi/design-system/Flex';
import { H3 } from '@strapi/design-system/Text';
import { useTracking } from '@strapi/helper-plugin';
import { ModalFooter } from '@strapi/design-system/ModalLayout';
import { Button } from '@strapi/design-system/Button';
import PicturePlus from '@strapi/icons/PicturePlus';
@ -34,10 +35,11 @@ const OpaqueBox = styled(Box)`
cursor: pointer;
`;
export const FromComputerForm = ({ onClose, onAddAssets }) => {
export const FromComputerForm = ({ onClose, onAddAssets, trackedLocation }) => {
const { formatMessage } = useIntl();
const [dragOver, setDragOver] = useState(false);
const inputRef = useRef(null);
const { trackUsage } = useTracking();
const handleDragEnter = () => setDragOver(true);
const handleDragLeave = () => setDragOver(false);
@ -58,6 +60,10 @@ export const FromComputerForm = ({ onClose, onAddAssets }) => {
assets.push(asset);
}
if (trackedLocation) {
trackUsage('didSelectFile', { source: 'computer', location: trackedLocation });
}
onAddAssets(assets);
};
@ -136,7 +142,12 @@ export const FromComputerForm = ({ onClose, onAddAssets }) => {
);
};
FromComputerForm.defaultProps = {
trackedLocation: undefined,
};
FromComputerForm.propTypes = {
onClose: PropTypes.func.isRequired,
onAddAssets: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -5,16 +5,17 @@ import { ModalFooter } from '@strapi/design-system/ModalLayout';
import { Textarea } from '@strapi/design-system/Textarea';
import { useIntl } from 'react-intl';
import { Button } from '@strapi/design-system/Button';
import { Form } from '@strapi/helper-plugin';
import { Form, useTracking } from '@strapi/helper-plugin';
import { Formik } from 'formik';
import getTrad from '../../../utils/getTrad';
import { urlSchema } from '../../../utils/urlYupSchema';
import { urlsToAssets } from '../../../utils/urlsToAssets';
export const FromUrlForm = ({ onClose, onAddAsset }) => {
export const FromUrlForm = ({ onClose, onAddAsset, trackedLocation }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(undefined);
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const handleSubmit = async ({ urls }) => {
setLoading(true);
@ -22,6 +23,10 @@ export const FromUrlForm = ({ onClose, onAddAsset }) => {
try {
const assets = await urlsToAssets(urlArray);
if (trackedLocation) {
trackUsage('didSelectFile', { source: 'url', location: trackedLocation });
}
// no need to set the loading to false since the component unmounts
onAddAsset(assets);
} catch (e) {
@ -83,7 +88,12 @@ export const FromUrlForm = ({ onClose, onAddAsset }) => {
);
};
FromUrlForm.defaultProps = {
trackedLocation: undefined,
};
FromUrlForm.propTypes = {
onClose: PropTypes.func.isRequired,
onAddAsset: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -106,55 +106,6 @@ describe('FromComputerForm', () => {
color: #666687;
}
.c16 {
background: #f6f6f9;
padding-top: 16px;
padding-right: 20px;
padding-bottom: 16px;
padding-left: 20px;
}
.c18 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c19 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c17 {
border-radius: 0 0 4px 4px;
border-top: 1px solid #eaeaef;
}
.c20 > * + * {
margin-left: 8px;
}
.c15 {
font-weight: 600;
color: #32324d;
@ -232,7 +183,7 @@ describe('FromComputerForm', () => {
background: #4945ff;
}
.c13 .sc-jcRCNh {
.c13 .sc-gfaqzF {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -297,7 +248,7 @@ describe('FromComputerForm', () => {
background: #ffffff;
}
.c21 .sc-jcRCNh {
.c21 .sc-gfaqzF {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -357,6 +308,55 @@ describe('FromComputerForm', () => {
fill: #32324d;
}
.c16 {
background: #f6f6f9;
padding-top: 16px;
padding-right: 20px;
padding-bottom: 16px;
padding-left: 20px;
}
.c18 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c19 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c17 {
border-radius: 0 0 4px 4px;
border-top: 1px solid #eaeaef;
}
.c20 > * + * {
margin-left: 8px;
}
.c5 {
-webkit-flex-direction: column;
-ms-flex-direction: column;

View File

@ -10,7 +10,12 @@ const Steps = {
PendingAsset: 'PendingAsset',
};
export const UploadAssetDialog = ({ initialAssetsToAdd, onClose, addUploadedFiles }) => {
export const UploadAssetDialog = ({
initialAssetsToAdd,
onClose,
addUploadedFiles,
trackedLocation,
}) => {
const [step, setStep] = useState(initialAssetsToAdd ? Steps.PendingAsset : Steps.AddAsset);
const [assets, setAssets] = useState(initialAssetsToAdd || []);
@ -45,7 +50,11 @@ export const UploadAssetDialog = ({ initialAssetsToAdd, onClose, addUploadedFile
return (
<ModalLayout onClose={onClose} labelledBy="title">
{step === Steps.AddAsset && (
<AddAssetStep onClose={onClose} onAddAsset={handleAddToPendingAssets} />
<AddAssetStep
onClose={onClose}
onAddAsset={handleAddToPendingAssets}
trackedLocation={trackedLocation}
/>
)}
{step === Steps.PendingAsset && (
<PendingAssetStep
@ -65,10 +74,12 @@ export const UploadAssetDialog = ({ initialAssetsToAdd, onClose, addUploadedFile
UploadAssetDialog.defaultProps = {
addUploadedFiles: undefined,
initialAssetsToAdd: undefined,
trackedLocation: undefined,
};
UploadAssetDialog.propTypes = {
addUploadedFiles: PropTypes.func,
initialAssetsToAdd: PropTypes.arrayOf(AssetDefinition),
onClose: PropTypes.func.isRequired,
trackedLocation: PropTypes.string,
};

View File

@ -198,7 +198,9 @@ export const MediaLibrary = () => {
</ContentLayout>
</Main>
{showUploadAssetDialog && <UploadAssetDialog onClose={toggleUploadAssetDialog} />}
{showUploadAssetDialog && (
<UploadAssetDialog onClose={toggleUploadAssetDialog} trackedLocation="upload" />
)}
{assetToEdit && (
<EditAssetDialog
onClose={() => setAssetToEdit(undefined)}
@ -206,6 +208,7 @@ export const MediaLibrary = () => {
canUpdate={canUpdate}
canCopyLink={canCopyLink}
canDownload={canDownload}
trackedLocation="upload"
/>
)}
</Layout>