diff --git a/src/storyMap/components/StoryMapForm/ShareDialog.js b/src/storyMap/components/StoryMapForm/ShareDialog.js
index a80666e36..eda595c9a 100644
--- a/src/storyMap/components/StoryMapForm/ShareDialog.js
+++ b/src/storyMap/components/StoryMapForm/ShareDialog.js
@@ -40,6 +40,7 @@ import MembershipsList from 'collaboration/components/MembershipsList';
import ConfirmButton from 'common/components/ConfirmButton';
import ExternalLink from 'common/components/ExternalLink';
import UserEmailAutocomplete from 'common/components/UserEmailAutocomplete';
+import { useAnalytics } from 'monitoring/analytics';
import Restricted from 'permissions/components/Restricted';
import {
MEMBERSHIP_ROLE_EDITOR,
@@ -74,6 +75,7 @@ const RoleComponent = ({ member }) => {
const RemoveButton = props => {
const dispatch = useDispatch();
const navigate = useNavigate();
+ const { trackEvent } = useAnalytics();
const { data: currentUser } = useSelector(state => state.account.currentUser);
const processing = useSelector(
state =>
@@ -100,9 +102,10 @@ const RemoveButton = props => {
if (isOwnMembership) {
navigate(-1);
}
+ trackEvent('storymap.share.remove');
}
});
- }, [dispatch, navigate, member, storyMap, isOwnMembership]);
+ }, [dispatch, navigate, trackEvent, member, storyMap, isOwnMembership]);
const resource = useMemo(() => {
if (!member?.userRole || member?.userRole === MEMBERSHIP_ROLE_OWNER) {
@@ -162,6 +165,7 @@ const RemoveButton = props => {
const ShareDialog = props => {
const dispatch = useDispatch();
const { t } = useTranslation();
+ const { trackEvent } = useAnalytics();
const processing = useSelector(
state => state.storyMap.memberships.add.saving
);
@@ -192,10 +196,15 @@ const ShareDialog = props => {
const success = data?.meta?.requestStatus === 'fulfilled';
if (success) {
setNewEditors([]);
+ trackEvent('storymap.share.invite', {
+ props: {
+ count: newEditors.length,
+ },
+ });
onClose();
}
});
- }, [dispatch, onClose, storyMap, newEditors]);
+ }, [dispatch, trackEvent, onClose, storyMap, newEditors]);
const memberships = useMemo(
() => [
diff --git a/src/storyMap/components/StoryMapInvite.js b/src/storyMap/components/StoryMapInvite.js
index 4bf35b26b..7caa23c22 100644
--- a/src/storyMap/components/StoryMapInvite.js
+++ b/src/storyMap/components/StoryMapInvite.js
@@ -26,12 +26,14 @@ import { Alert } from '@mui/material';
import { useDocumentTitle } from 'common/document';
import PageContainer from 'layout/PageContainer';
import PageLoader from 'layout/PageLoader';
+import { useAnalytics } from 'monitoring/analytics';
import { approveMembershipToken } from 'storyMap/storyMapSlice';
const StoryMapInvite = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
+ const { trackEvent } = useAnalytics();
const [searchParams] = useSearchParams();
const token = useMemo(() => searchParams.get('token'), [searchParams]);
const decodedToken = useMemo(() => (token ? jwt(token) : null), [token]);
@@ -82,7 +84,8 @@ const StoryMapInvite = () => {
return;
}
navigate(`/tools/story-maps/${storyMap.storyMapId}/${storyMap.slug}/edit`);
- }, [success, navigate, storyMap]);
+ trackEvent('storymap.share.accept');
+ }, [success, navigate, trackEvent, storyMap]);
useDocumentTitle(t('storyMap.invite_document_title'));
diff --git a/src/storyMap/components/StoryMapInvite.test.js b/src/storyMap/components/StoryMapInvite.test.js
index 7ce228c29..eee7b65dc 100644
--- a/src/storyMap/components/StoryMapInvite.test.js
+++ b/src/storyMap/components/StoryMapInvite.test.js
@@ -18,6 +18,8 @@ import { render, screen, waitFor, within } from 'tests/utils';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { mockTerrasoAPIrequestGraphQL } from 'tests/apiUtils';
+import { useAnalytics } from 'monitoring/analytics';
+
import StoryMapInvite from './StoryMapInvite';
// Generated token with https://jwt.io/
@@ -38,11 +40,27 @@ jest.mock('react-router-dom', () => ({
useSearchParams: jest.fn(),
}));
+jest.mock('monitoring/analytics', () => ({
+ ...jest.requireActual('monitoring/analytics'),
+ useAnalytics: jest.fn(),
+}));
+
+beforeEach(() => {
+ useAnalytics.mockReturnValue({
+ trackEvent: jest.fn(),
+ });
+});
+
const setup = async initialState => {
await render(, initialState);
};
test('StoryMapInvite: Valid token', async () => {
+ const trackEvent = jest.fn();
+ useAnalytics.mockReturnValue({
+ trackEvent,
+ });
+
const navigate = jest.fn();
useNavigate.mockReturnValue(navigate);
@@ -79,6 +97,8 @@ test('StoryMapInvite: Valid token', async () => {
expect(navigate.mock.calls[0]).toEqual([
'/tools/story-maps/story-map-id-1/hello-world/edit',
]);
+
+ expect(trackEvent).toHaveBeenCalledWith('storymap.share.accept');
});
test('StoryMapInvite: Invalid token', async () => {
diff --git a/src/storyMap/components/StoryMapUpdate.test.js b/src/storyMap/components/StoryMapUpdate.test.js
index 7f7c51a4a..cf31d3d31 100644
--- a/src/storyMap/components/StoryMapUpdate.test.js
+++ b/src/storyMap/components/StoryMapUpdate.test.js
@@ -21,6 +21,7 @@ import { mockTerrasoAPIrequestGraphQL } from 'tests/apiUtils';
import { changeCombobox } from 'tests/uiUtils';
import i18n from 'localization/i18n';
+import { useAnalytics } from 'monitoring/analytics';
import { MEMBERSHIP_ROLE_EDITOR } from 'storyMap/storyMapConstants';
import StoryMapUpdate from './StoryMapUpdate';
@@ -29,6 +30,11 @@ jest.mock('terraso-client-shared/terrasoApi/api');
jest.mock('./StoryMap', () => props =>
Test
);
+jest.mock('monitoring/analytics', () => ({
+ ...jest.requireActual('monitoring/analytics'),
+ useAnalytics: jest.fn(),
+}));
+
const CONFIG = {
title: 'Story Map Title',
subtitle: 'Story Map Subtitle',
@@ -92,6 +98,12 @@ const API_STORY_MAP = {
},
};
+beforeEach(() => {
+ useAnalytics.mockReturnValue({
+ trackEvent: jest.fn(),
+ });
+});
+
const setup = async user => {
await render(, {
account: {
@@ -177,6 +189,10 @@ test('StoryMapUpdate: Show Share Dialog', async () => {
});
test('StoryMapUpdate: Share Dialog invite members', async () => {
+ const trackEvent = jest.fn();
+ useAnalytics.mockReturnValue({
+ trackEvent,
+ });
mockTerrasoAPIrequestGraphQL({
'query fetchStoryMap': Promise.resolve({
storyMaps: {
@@ -245,9 +261,19 @@ test('StoryMapUpdate: Share Dialog invite members', async () => {
expect(
within(membersList).getByRole('listitem', { name: 'Manuel Perez' })
).toBeInTheDocument();
+
+ expect(trackEvent).toHaveBeenCalledWith('storymap.share.invite', {
+ props: {
+ count: 2,
+ },
+ });
});
test('StoryMapUpdate: Share Dialog remove members', async () => {
+ const trackEvent = jest.fn();
+ useAnalytics.mockReturnValue({
+ trackEvent,
+ });
mockTerrasoAPIrequestGraphQL({
'query fetchStoryMap': Promise.resolve({
storyMaps: {
@@ -290,6 +316,8 @@ test('StoryMapUpdate: Share Dialog remove members', async () => {
storyMapSlug: 'test-slug',
},
});
+
+ expect(trackEvent).toHaveBeenCalledWith('storymap.share.remove');
});
test('StoryMapUpdate: See story map as editor', async () => {
diff --git a/src/storyMap/components/StoryMapsCard.js b/src/storyMap/components/StoryMapsCard.js
index 67c47f798..627bc23cf 100644
--- a/src/storyMap/components/StoryMapsCard.js
+++ b/src/storyMap/components/StoryMapsCard.js
@@ -39,6 +39,7 @@ import { MEMBERSHIP_STATUS_PENDING } from 'collaboration/collaborationConstants'
import RouterButton from 'common/components/RouterButton';
import RouterLink from 'common/components/RouterLink';
import { formatDate } from 'localization/utils';
+import { useAnalytics } from 'monitoring/analytics';
import Restricted from 'permissions/components/Restricted';
import HomeCard from 'home/components/HomeCard';
import { approveMembership, removeUserStoryMap } from 'storyMap/storyMapSlice';
@@ -75,6 +76,7 @@ const CollaborationIndicator = props => {
const StoryMapListItem = props => {
const dispatch = useDispatch();
const navigate = useNavigate();
+ const { trackEvent } = useAnalytics();
const { t, i18n } = useTranslation();
const { storyMap } = props;
@@ -106,9 +108,10 @@ const StoryMapListItem = props => {
const storyMapId = data.payload.storyMap.storyMapId;
const storyMapSlug = data.payload.storyMap.slug;
navigate(`/tools/story-maps/${storyMapId}/${storyMapSlug}/edit`);
+ trackEvent('storymap.share.accept');
}
});
- }, [dispatch, navigate, storyMap, accountMembership]);
+ }, [dispatch, navigate, trackEvent, storyMap, accountMembership]);
const onDeleteSuccess = useCallback(() => {
dispatch(removeUserStoryMap(storyMap.id));
diff --git a/src/storyMap/components/StoryMapsToolHome.test.js b/src/storyMap/components/StoryMapsToolHome.test.js
index 8b1b51bcf..0c40b4318 100644
--- a/src/storyMap/components/StoryMapsToolHome.test.js
+++ b/src/storyMap/components/StoryMapsToolHome.test.js
@@ -14,13 +14,27 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-import { render, screen, within } from 'tests/utils';
+import { act, fireEvent, render, screen, within } from 'tests/utils';
import * as terrasoApi from 'terraso-client-shared/terrasoApi/api';
+import { mockTerrasoAPIrequestGraphQL } from 'tests/apiUtils';
+
+import { useAnalytics } from 'monitoring/analytics';
import StoryMapsToolsHome from './StoryMapsToolHome';
jest.mock('terraso-client-shared/terrasoApi/api');
+jest.mock('monitoring/analytics', () => ({
+ ...jest.requireActual('monitoring/analytics'),
+ useAnalytics: jest.fn(),
+}));
+
+beforeEach(() => {
+ useAnalytics.mockReturnValue({
+ trackEvent: jest.fn(),
+ });
+});
+
test('StoryMapsToolHome: samples renders correctly', async () => {
terrasoApi.requestGraphQL.mockReturnValue(
Promise.resolve({
@@ -158,3 +172,77 @@ test('StoryMapsToolHome: user story maps render correctly', async () => {
const link1 = within(items[1]).getByRole('link', { name: 'Story 1' });
expect(link1).toHaveAttribute('href', '/tools/story-maps/46h36we/id-1/edit');
});
+
+test('StoryMapsToolHome: accept story map invite', async () => {
+ const trackEvent = jest.fn();
+ useAnalytics.mockReturnValue({
+ trackEvent,
+ });
+ mockTerrasoAPIrequestGraphQL({
+ 'query storyMapsHome': Promise.resolve({
+ userStoryMaps: {
+ edges: [
+ {
+ node: {
+ id: 'id-1',
+ slug: 'id-1',
+ storyMapId: '46h36we',
+ title: 'Story 1',
+ isPublished: false,
+ updatedAt: '2023-01-31T22:25:42.916303+00:00',
+ createdBy: {
+ userId: 'other-user-id',
+ firstName: 'Pablo',
+ lastName: 'Perez',
+ },
+ membershipList: {
+ membershipsCount: 0,
+ accountMembership: {
+ id: '12eb041f-e847-4f78-89ec-46a6a6b7c5c6',
+ userRole: 'editor',
+ membershipStatus: 'PENDING',
+ },
+ },
+ },
+ },
+ ],
+ },
+ }),
+ 'mutation approveMembership': Promise.resolve({
+ approveStoryMapMembership: {
+ membership: {
+ id: 'membership-id-1',
+ },
+ storyMap: {
+ id: 'story-map-id-1',
+ title: 'Hello world',
+ storyMapId: 'story-map-id-1',
+ slug: 'hello-world',
+ },
+ },
+ }),
+ });
+
+ await render(, {
+ account: {
+ currentUser: {
+ data: {
+ email: 'account@email.com',
+ firstName: 'Jodies',
+ },
+ },
+ },
+ });
+
+ const storyMapItem = screen.getByRole('listitem', { name: 'Story 1' });
+
+ const acceptButton = within(storyMapItem).getByRole('button', {
+ name: 'Accept',
+ });
+
+ await act(async () => {
+ fireEvent.click(acceptButton);
+ });
+
+ expect(trackEvent).toHaveBeenCalledWith('storymap.share.accept');
+});