From 55f68fc225390786a5db593ed83fdc3b478016fa Mon Sep 17 00:00:00 2001 From: psjostrom Date: Fri, 27 Nov 2020 17:07:46 +0100 Subject: [PATCH] SDA-2571 SDA-2721 Add annotate BI and fix window size on windows --- spec/annotateArea.spec.tsx | 11 ++++++++++ spec/snippingTool.spec.tsx | 22 +++++++++++++++++++ src/app/analytics-handler.ts | 15 +++++++++---- src/app/window-handler.ts | 4 ++-- src/renderer/components/annotate-area.tsx | 11 +++++++++- src/renderer/components/snipping-tool.tsx | 26 +++++++++++++++++++---- 6 files changed, 78 insertions(+), 11 deletions(-) diff --git a/spec/annotateArea.spec.tsx b/spec/annotateArea.spec.tsx index 700f1b23..2359d14a 100644 --- a/spec/annotateArea.spec.tsx +++ b/spec/annotateArea.spec.tsx @@ -2,6 +2,7 @@ import { mount } from 'enzyme'; import * as React from 'react'; import AnnotateArea from '../src/renderer/components/annotate-area'; import { Tool } from '../src/renderer/components/snipping-tool'; +import * as analyticsHandler from './../src/app/analytics-handler'; const defaultProps = { paths: [], @@ -124,4 +125,14 @@ describe('', () => { strokeWidth: 5, }]); }); + + it('should send capture_taken BI event on component mount', () => { + const spy = jest.spyOn(analyticsHandler.analytics, 'track'); + const expectedValue = { action_type: 'annotate_added', element: 'ScreenSnippet' }; + const wrapper = mount(); + const area = wrapper.find('[data-testid="annotate-area"]'); + area.simulate('mousedown', { pageX: 2, pageY: 49 }); + area.simulate('mouseup'); + expect(spy).toBeCalledWith(expectedValue); + }); }); diff --git a/spec/snippingTool.spec.tsx b/spec/snippingTool.spec.tsx index badb3b62..a769e704 100644 --- a/spec/snippingTool.spec.tsx +++ b/spec/snippingTool.spec.tsx @@ -1,10 +1,15 @@ import { mount, shallow } from 'enzyme'; import * as React from 'react'; import SnippingTool from '../src/renderer/components/snipping-tool'; +import * as analyticsHandler from './../src/app/analytics-handler'; import { ipcRenderer } from './__mocks__/electron'; const waitForPromisesToResolve = () => new Promise((resolve) => setTimeout(resolve)); +afterEach(() => { + jest.clearAllMocks(); +}); + describe('Snipping Tool', () => { it('should render correctly', () => { const wrapper = shallow(React.createElement(SnippingTool)); @@ -17,6 +22,23 @@ describe('Snipping Tool', () => { expect(spy).toBeCalledWith('snipping-tool-data', expect.any(Function)); }); + it('should send capture_taken BI event on component mount', () => { + const spy = jest.spyOn(analyticsHandler.analytics, 'track'); + const expectedValue = { action_type: 'capture_taken', element: 'ScreenSnippet' }; + mount(React.createElement(SnippingTool)); + expect(spy).toBeCalledWith(expectedValue); + }); + + it('should send capture_sent BI event when clicking done', async () => { + const spy = jest.spyOn(analyticsHandler.analytics, 'track'); + const expectedValue = { action_type: 'capture_sent', element: 'ScreenSnippet' }; + const wrapper = mount(React.createElement(SnippingTool)); + wrapper.find('[data-testid="done-button"]').simulate('click'); + wrapper.update(); + await waitForPromisesToResolve(); + expect(spy).toBeCalledWith(expectedValue); + }); + it('should render pen color picker when clicked on pen', () => { const wrapper = shallow(React.createElement(SnippingTool)); wrapper.find('[data-testid="pen-button"]').simulate('click'); diff --git a/src/app/analytics-handler.ts b/src/app/analytics-handler.ts index 6cb58dec..fb585c5e 100644 --- a/src/app/analytics-handler.ts +++ b/src/app/analytics-handler.ts @@ -1,7 +1,7 @@ export interface IAnalyticsData { - element: string; - action_type: MenuActionTypes; - action_result: string; + element: AnalyticsElements; + action_type: MenuActionTypes | ScreenSnippetActionTypes; + action_result?: AnalyticsActions; } export enum MenuActionTypes { @@ -13,6 +13,12 @@ export enum MenuActionTypes { REFRESH_APP_IN_IDLE = 'refresh_app_in_idle', } +export enum ScreenSnippetActionTypes { + CAPTURE_TAKEN = 'capture_taken', + ANNOTATE_ADDED = 'annotate_added', + CAPTURE_SENT = 'capture_sent', +} + export enum AnalyticsActions { ENABLED = 'ON', DISABLED = 'OFF', @@ -20,6 +26,7 @@ export enum AnalyticsActions { export enum AnalyticsElements { MENU = 'Menu', + SCREEN_SNIPPET = 'ScreenSnippet', } const MAX_EVENT_QUEUE_LENGTH = 50; @@ -61,7 +68,7 @@ class Analytics { return; } this.analyticsEventQueue.push(eventData); - // don't store more than 100 msgs. keep most recent log msgs. + // don't store more than 50 msgs. keep most recent log msgs. if (this.analyticsEventQueue.length > MAX_EVENT_QUEUE_LENGTH) { this.analyticsEventQueue.shift(); } diff --git a/src/app/window-handler.ts b/src/app/window-handler.ts index 83e53659..10a26958 100644 --- a/src/app/window-handler.ts +++ b/src/app/window-handler.ts @@ -991,8 +991,8 @@ export class WindowHandler { const opts: ICustomBrowserWindowConstructorOpts = this.getWindowOpts( { - width: annotateAreaWidth, - height: windowHeight, + width: annotateAreaWidth < MIN_WIDTH ? MIN_WIDTH : annotateAreaWidth, + height: windowHeight < MIN_HEIGHT ? MIN_HEIGHT : windowHeight, minHeight: MIN_HEIGHT, minWidth: MIN_WIDTH, modal: false, diff --git a/src/renderer/components/annotate-area.tsx b/src/renderer/components/annotate-area.tsx index 3d06bcc4..a801ebd0 100644 --- a/src/renderer/components/annotate-area.tsx +++ b/src/renderer/components/annotate-area.tsx @@ -1,5 +1,6 @@ import { LazyBrush } from 'lazy-brush'; import * as React from 'react'; +import { analytics, AnalyticsElements, ScreenSnippetActionTypes } from './../../app/analytics-handler'; import { IDimensions, IPath, IPoint, Tool } from './snipping-tool'; const { useState } = React; @@ -224,6 +225,14 @@ const AnnotateArea: React.FunctionComponent = ({ addPathPoint(e); }; + const handleMouseUp = () => { + stopDrawing(); + analytics.track({ + element: AnalyticsElements.SCREEN_SNIPPET, + action_type: ScreenSnippetActionTypes.ANNOTATE_ADDED, + }); + }; + const getAnnotateWrapperStyle = () => { const shouldShowScrollBars = imageDimensions.height > annotateAreaDimensions.height || @@ -246,7 +255,7 @@ const AnnotateArea: React.FunctionComponent = ({ width={imageDimensions.width} height={imageDimensions.height} onMouseDown={handleMouseDown} - onMouseUp={stopDrawing} + onMouseUp={handleMouseUp} onMouseMove={handleMouseMove} onMouseLeave={stopDrawing} > diff --git a/src/renderer/components/snipping-tool.tsx b/src/renderer/components/snipping-tool.tsx index 4bfa5e56..21f87cf3 100644 --- a/src/renderer/components/snipping-tool.tsx +++ b/src/renderer/components/snipping-tool.tsx @@ -1,6 +1,7 @@ import { ipcRenderer } from 'electron'; import * as React from 'react'; import { i18n } from '../../common/i18n-preload'; +import { analytics, AnalyticsElements, ScreenSnippetActionTypes } from './../../app/analytics-handler'; import AnnotateArea from './annotate-area'; import ColorPickerPill, { IColor } from './color-picker-pill'; @@ -89,9 +90,13 @@ const SnippingTool: React.FunctionComponent = ({ existingPat useLayoutEffect(() => { ipcRenderer.once('snipping-tool-data', getSnipImageData); - return () => { - ipcRenderer.removeListener('snipping-tool-data', getSnipImageData); - }; + analytics.track({ + element: AnalyticsElements.SCREEN_SNIPPET, + action_type: ScreenSnippetActionTypes.CAPTURE_TAKEN, + }); + return () => { + ipcRenderer.removeListener('snipping-tool-data', getSnipImageData); + }; }, []); // Hook that alerts clicks outside of the passed refs @@ -176,6 +181,16 @@ const SnippingTool: React.FunctionComponent = ({ existingPat return 'NO CANVAS'; } + const backgroundImage = document.getElementById('backgroundImage') as HTMLImageElement; + + // Fast lane in case there is no drawn SVG paths + if (paths.length === 0) { + ctx.drawImage(backgroundImage, 0, 0); + // Extracts base 64 png img data from the canvas + const data = canvas.toDataURL('image/png'); + return data; + } + // Creates an in memory img without adding it to the DOM const img = document.createElement('img'); @@ -187,7 +202,6 @@ const SnippingTool: React.FunctionComponent = ({ existingPat 'src', 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))), ); - const backgroundImage = document.getElementById('backgroundImage') as HTMLImageElement; return new Promise((resolve, reject) => { // Listens to when the img is loaded in memory and adds the data from the SVG paths + screenSnippet to the canvas @@ -233,6 +247,10 @@ const SnippingTool: React.FunctionComponent = ({ existingPat const done = async () => { const base64PngData = await getBase64PngData(); + analytics.track({ + element: AnalyticsElements.SCREEN_SNIPPET, + action_type: ScreenSnippetActionTypes.CAPTURE_SENT, + }); ipcRenderer.send('upload-snippet', { screenSnippetPath, base64PngData }); };