From 6ace3305a1b8c2bfb93c1624ac97d918adbd9f3d Mon Sep 17 00:00:00 2001 From: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:54:27 +0530 Subject: [PATCH] feat: new split expense trackers (#2743) * feat: new split expense trackers * fix: file not attached in split expense fix (#2746) * minor --- .../core/models/tracking-properties.model.ts | 10 +++- src/app/core/services/tracking.service.ts | 16 ++++++ .../split-expense/split-expense.page.spec.ts | 41 ++++++++++++-- .../fyle/split-expense/split-expense.page.ts | 56 +++++++++++++++---- 4 files changed, 105 insertions(+), 18 deletions(-) diff --git a/src/app/core/models/tracking-properties.model.ts b/src/app/core/models/tracking-properties.model.ts index 5dedab6b5d..a3abfa2f64 100644 --- a/src/app/core/models/tracking-properties.model.ts +++ b/src/app/core/models/tracking-properties.model.ts @@ -3,6 +3,7 @@ import { Transaction } from './v1/transaction.model'; import { ExpenseView } from './expense-view.enum'; import { TaskFilters } from './task-filters.model'; import { CardNetworkType } from '../enums/card-network-type'; +import { SplitPayload } from './platform/v1/split-payload.model'; export interface TrackingMethods { identify(data: T, property?: K): void; @@ -39,8 +40,15 @@ export interface ExpenseProperties { } export interface SplittingExpenseProperties { - 'Split Type': string; + Type: string; 'Is Evenly Split': boolean; + Asset: string; + 'Is part of report': boolean; + 'Report ID': string; + 'Expense State': string; + 'User Role': string; + 'Error Message'?: string; + 'Split Payload'?: SplitPayload; } export interface PolicyCorrectionProperties { diff --git a/src/app/core/services/tracking.service.ts b/src/app/core/services/tracking.service.ts index 49807e3308..140462b4d3 100644 --- a/src/app/core/services/tracking.service.ts +++ b/src/app/core/services/tracking.service.ts @@ -141,6 +141,22 @@ export class TrackingService { this.eventTrack('Splitting Expense', properties); } + splitExpenseSuccess(properties: SplittingExpenseProperties): void { + this.eventTrack('Split Expense Success', properties); + } + + splitExpenseFailed(properties: SplittingExpenseProperties): void { + this.eventTrack('Split Expense Failed', properties); + } + + splitExpensePolicyCheckFailed(properties: SplittingExpenseProperties): void { + this.eventTrack('Split Expense Policy check failed', properties); + } + + splitExpensePolicyAndMissingFieldsPopupShown(properties: SplittingExpenseProperties): void { + this.eventTrack('Split Expense Policy and Missing Fields Popup Shown', properties); + } + // view expense event viewExpense(properties: { Type: string }): void { this.eventTrack('View Expense', properties); diff --git a/src/app/fyle/split-expense/split-expense.page.spec.ts b/src/app/fyle/split-expense/split-expense.page.spec.ts index b7d1a1bb2e..ff23d76fbf 100644 --- a/src/app/fyle/split-expense/split-expense.page.spec.ts +++ b/src/app/fyle/split-expense/split-expense.page.spec.ts @@ -168,6 +168,7 @@ import { } from 'src/app/core/mock-data/transformed-split-expense-missing-fields.data'; import { splitPolicyExp1 } from 'src/app/core/mock-data/split-expense-policy.data'; import { SplitExpenseMissingFieldsData } from 'src/app/core/models/split-expense-missing-fields.data'; +import { splitPayloadData1 } from 'src/app/core/mock-data/split-payload.data'; describe('SplitExpensePage', () => { let component: SplitExpensePage; @@ -218,6 +219,7 @@ describe('SplitExpensePage', () => { 'filteredPolicyViolations', 'handlePolicyAndMissingFieldsCheck', 'checkIfMissingFieldsExist', + 'transformSplitTo', ]); const currencyServiceSpy = jasmine.createSpyObj('CurrencyService', ['getHomeCurrency']); const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['delete', 'matchCCCExpense']); @@ -227,7 +229,14 @@ describe('SplitExpensePage', () => { const reportServiceSpy = jasmine.createSpyObj('ReportService', ['addTransactions']); const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); const snackbarPropertiesSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); - const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['showToastMessage', 'splittingExpense']); + const trackingServiceSpy = jasmine.createSpyObj('TrackingService', [ + 'showToastMessage', + 'splittingExpense', + 'splitExpenseSuccess', + 'splitExpenseFailed', + 'splitExpensePolicyCheckFailed', + 'splitExpensePolicyAndMissingFieldsPopupShown', + ]); const policyServiceSpy = jasmine.createSpyObj('PolicyService', ['checkIfViolationsExist']); const modalControllerSpy = jasmine.createSpyObj('ModalController', ['create', 'getTop']); const modalPropertiesSpy = jasmine.createSpyObj('ModalPropertiesService', ['getModalDefaultProperties']); @@ -1754,8 +1763,13 @@ describe('SplitExpensePage', () => { transformedOrgCategories ); expect(trackingService.splittingExpense).toHaveBeenCalledOnceWith({ - 'Split Type': 'projects', + Type: 'projects', 'Is Evenly Split': true, + Asset: 'Mobile', + 'Is part of report': false, + 'Report ID': null, + 'User Role': 'spender', + 'Expense State': 'DRAFT', }); expect(component.handleSplitExpensePolicyViolations).toHaveBeenCalledOnceWith(policyVoilationData2); }); @@ -1781,8 +1795,13 @@ describe('SplitExpensePage', () => { ); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_expenses']); expect(trackingService.splittingExpense).toHaveBeenCalledOnceWith({ - 'Split Type': 'projects', + Type: 'projects', 'Is Evenly Split': true, + Asset: 'Mobile', + 'Is part of report': false, + 'Report ID': null, + 'User Role': 'spender', + 'Expense State': 'DRAFT', }); expect(component.handleSplitExpensePolicyViolations).not.toHaveBeenCalled(); } @@ -2254,8 +2273,13 @@ describe('SplitExpensePage', () => { expect(component.generateSplitEtxnFromFg).toHaveBeenCalledOnceWith(component.splitExpensesFormArray.value[0]); expect(component.handlePolicyAndMissingFieldsCheck).toHaveBeenCalledOnceWith(txnList); expect(trackingService.splittingExpense).toHaveBeenCalledOnceWith({ - 'Split Type': 'projects', + Type: 'projects', 'Is Evenly Split': true, + Asset: 'Mobile', + 'Is part of report': false, + 'Report ID': null, + 'User Role': 'spender', + 'Expense State': 'DRAFT', }); expect(component.handleSplitExpense).toHaveBeenCalledOnceWith({ '0': 'test comment' }); }); @@ -2268,6 +2292,7 @@ describe('SplitExpensePage', () => { .createSpy() .and.returnValue(throwError(() => new Error('Policy Violation checks were failed!'))); spyOn(component, 'toastWithoutCTA'); + splitExpenseService.transformSplitTo.and.returnValue(splitPayloadData1); try { component.saveV2(); @@ -2280,8 +2305,13 @@ describe('SplitExpensePage', () => { 'msb-failure-with-camera-icon' ); expect(trackingService.splittingExpense).toHaveBeenCalledOnceWith({ - 'Split Type': 'projects', + Type: 'projects', 'Is Evenly Split': true, + Asset: 'Mobile', + 'Is part of report': false, + 'Report ID': null, + 'User Role': 'spender', + 'Expense State': 'DRAFT', }); expect(component.handleSplitExpense).not.toHaveBeenCalled(); } @@ -2396,6 +2426,7 @@ describe('SplitExpensePage', () => { handle: false, }; modalProperties.getModalDefaultProperties.and.returnValue(properties); + component.transaction = cloneDeep(txnData4); }); it('should open policy violations and missing fields modal', async () => { diff --git a/src/app/fyle/split-expense/split-expense.page.ts b/src/app/fyle/split-expense/split-expense.page.ts index ae29b832df..66f046d166 100644 --- a/src/app/fyle/split-expense/split-expense.page.ts +++ b/src/app/fyle/split-expense/split-expense.page.ts @@ -49,6 +49,8 @@ import { TransformedSplitExpenseMissingFields } from 'src/app/core/models/transf import { SplitExpenseViolationsPopup } from 'src/app/core/models/split-expense-violations-popup.model'; import { TimezoneService } from 'src/app/core/services/timezone.service'; import { TxnCustomProperties } from 'src/app/core/models/txn-custom-properties.model'; +import { SplittingExpenseProperties } from 'src/app/core/models/tracking-properties.model'; +import { HttpErrorResponse } from '@angular/common/http'; @Component({ selector: 'app-split-expense', @@ -640,6 +642,9 @@ export class SplitExpensePage { filteredMissingFieldsViolations = null; } + const splitTrackingProps = this.getSplitExpensePoperties(); + this.trackingService.splitExpensePolicyAndMissingFieldsPopupShown(splitTrackingProps); + const splitExpenseViolationsModal = await this.modalController.create({ component: SplitExpensePolicyViolationComponent, componentProps: { @@ -717,14 +722,21 @@ export class SplitExpensePage { this.splitExpenseService .splitExpense(this.formattedSplitExpense, this.fileObjs, this.transaction, reportAndCategoryParams) .pipe( - catchError((err) => { + catchError((errResponse: HttpErrorResponse) => { + const splitTrackingProps = this.getSplitExpensePoperties(); + splitTrackingProps['Error Message'] = (errResponse?.error as { message: string })?.message; + this.trackingService.splitExpensePolicyCheckFailed(splitTrackingProps); + const message = 'We were unable to split your expense. Please try again later.'; this.toastWithoutCTA(message, ToastType.FAILURE, 'msb-failure-with-camera-icon'); this.router.navigate(['/', 'enterprise', 'my_expenses']); - return throwError(err); + return throwError(errResponse); }) ) .subscribe((txns) => { + const splitTrackingProps = this.getSplitExpensePoperties(); + this.trackingService.splitExpenseSuccess(splitTrackingProps); + const txnIds = txns.data.map((txn) => txn.id); if (comments) { @@ -759,6 +771,18 @@ export class SplitExpensePage { } } + getSplitExpensePoperties(): SplittingExpenseProperties { + return { + Type: this.splitType, + 'Is Evenly Split': this.isEvenlySplit(), + Asset: 'Mobile', + 'Is part of report': !!this.reportId, + 'Report ID': this.reportId || null, + 'Expense State': this.transaction.state, + 'User Role': 'spender', + }; + } + saveV2(): void { if (this.splitExpensesFormArray.valid) { this.showErrorBlock = false; @@ -801,6 +825,7 @@ export class SplitExpensePage { forkJoin({ generatedSplitEtxn: forkJoin(generatedSplitEtxn$), + files: this.uploadFiles(this.fileUrls), }) .pipe( concatMap(({ generatedSplitEtxn }) => this.createSplitTxns(generatedSplitEtxn)), @@ -809,18 +834,28 @@ export class SplitExpensePage { this.correctTotalSplitAmount(); return this.handlePolicyAndMissingFieldsCheck(formattedSplitExpense); }), - catchError((err) => { + catchError((errResponse: HttpErrorResponse) => { + const splitTrackingProps = this.getSplitExpensePoperties(); + splitTrackingProps['Error Message'] = (errResponse?.error as { message: string })?.message; + + const fileIds = this.fileObjs?.map((file) => file.id); + splitTrackingProps['Split Payload'] = this.splitExpenseService.transformSplitTo( + this.formattedSplitExpense, + this.transaction, + fileIds, + { reportId: this.reportId, unspecifiedCategory: this.unspecifiedCategory } + ); + + this.trackingService.splitExpensePolicyCheckFailed(splitTrackingProps); + const message = 'We were unable to split your expense. Please try again later.'; this.toastWithoutCTA(message, ToastType.FAILURE, 'msb-failure-with-camera-icon'); - return throwError(err); + return throwError(errResponse); }), finalize(() => { this.saveSplitExpenseLoading = false; - const splitTrackingProps = { - 'Split Type': this.splitType, - 'Is Evenly Split': this.isEvenlySplit(), - }; + const splitTrackingProps = this.getSplitExpensePoperties(); this.trackingService.splittingExpense(splitTrackingProps); }) ) @@ -911,10 +946,7 @@ export class SplitExpensePage { finalize(() => { this.saveSplitExpenseLoading = false; - const splitTrackingProps = { - 'Split Type': this.splitType, - 'Is Evenly Split': this.isEvenlySplit(), - }; + const splitTrackingProps = this.getSplitExpensePoperties(); this.trackingService.splittingExpense(splitTrackingProps); }) )