Skip to content

Commit

Permalink
feat: mobile app refiner integration (#3236)
Browse files Browse the repository at this point in the history
* feat: mobile app refiner integration
  • Loading branch information
Z3RO-O authored Oct 25, 2024
1 parent 8dab1e1 commit bf4b3df
Show file tree
Hide file tree
Showing 18 changed files with 434 additions and 129 deletions.
3 changes: 2 additions & 1 deletion hooks/utils/prod-environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const environment = {
GOOGLE_MAPS_API_KEY: '${process.env.FYLE_MOBILE_GOOGLE_MAPS_API_KEY}',
FRESHCHAT_TOKEN: '${process.env.FYLE_MOBILE_FRESHCHAT_TOKEN}',
SENTRY_DSN: '${process.env.FYLE_MOBILE_SENTRY_DSN}',
REFINER_NPS_PROJECT_ID: '${process.env.REFINER_NPS_PROJECT_ID}',
REFINER_NPS_FORM_ID: '${process.env.REFINER_NPS_FORM_ID}',
LAUNCH_DARKLY_CLIENT_ID: '${process.env.LAUNCH_DARKLY_CLIENT_ID}',
LIVE_UPDATE_APP_VERSION: '${process.env.LIVE_UPDATE_APP_VERSION}',
Expand All @@ -20,4 +21,4 @@ export const environment = {
USE_MIXPANEL_PROXY: '${process.env.USE_MIXPANEL_PROXY}',
ENABLE_MIXPANEL: '${process.env.ENABLE_MIXPANEL}'
};
`
`;
151 changes: 136 additions & 15 deletions src/app/core/services/refiner.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { of } from 'rxjs';
import { apiEouRes } from '../mock-data/extended-org-user.data';
import { ExtendedOrgUser } from '../models/extended-org-user.model';

xdescribe('RefinerService', () => {
describe('RefinerService', () => {
let refinerService: RefinerService;
let currencyService: jasmine.SpyObj<CurrencyService>;
let authService: jasmine.SpyObj<AuthService>;
Expand Down Expand Up @@ -41,22 +41,58 @@ xdescribe('RefinerService', () => {
expect(refinerService).toBeTruthy();
});

it('setupNetworkWatcher(): should setup a network watcher', () => {
const emitterSpy = jasmine.createSpyObj('EventEmitter', ['asObservable']);
emitterSpy.asObservable.and.returnValue(of(true));
refinerService.setupNetworkWatcher();
networkService.isOnline.and.returnValue(of(true));
expect(networkService.connectivityWatcher).toHaveBeenCalledTimes(2);
expect(networkService.isOnline).toHaveBeenCalledTimes(2);
describe('setupNetworkWatcher', () => {
it('should setup a network watcher', () => {
const emitterSpy = jasmine.createSpyObj('EventEmitter', ['asObservable']);
emitterSpy.asObservable.and.returnValue(of(true));
refinerService.setupNetworkWatcher();
networkService.isOnline.and.returnValue(of(true));

expect(networkService.connectivityWatcher).toHaveBeenCalledTimes(2);
expect(networkService.isOnline).toHaveBeenCalledTimes(2);
});
});

it('getRegion(): should return region', () => {
expect(refinerService.getRegion('INR')).toEqual('India');
expect(refinerService.getRegion('USD')).toEqual('International Americas');
expect(refinerService.getRegion('EUR')).toEqual('Europe');
expect(refinerService.getRegion('AUD')).toEqual('International APAC');
expect(refinerService.getRegion('AED')).toEqual('International Africa');
expect(refinerService.getRegion('')).toEqual('Undefined');
describe('getRegion', () => {
it('should return "India" for INR currency', () => {
expect(refinerService.getRegion('INR')).toEqual('India');
});

it('should return "International Americas" for USD', () => {
expect(refinerService.getRegion('USD')).toEqual('International Americas');
});

it('should return correct region for APAC currency', () => {
expect(refinerService.getRegion('AUD')).toEqual('International APAC');
});

it('should return correct region for Middle East Africa currency', () => {
expect(refinerService.getRegion('AED')).toEqual('International Africa');
});

it('should return correct region for Europe currency', () => {
expect(refinerService.getRegion('EUR')).toEqual('Europe');
});

it('should return "International Africa" for another Middle East Africa currency', () => {
expect(refinerService.getRegion('ZAR')).toEqual('International Africa');
});

it('should return "Undefined" for unsupported currency', () => {
expect(refinerService.getRegion('XYZ')).toEqual('Undefined');
});

it('should return "Undefined" for null currency', () => {
expect(refinerService.getRegion(null)).toEqual('Undefined');
});

it('should return "Undefined" for undefined currency', () => {
expect(refinerService.getRegion(undefined)).toEqual('Undefined');
});

it('should return "Undefined" for empty currency', () => {
expect(refinerService.getRegion('')).toEqual('Undefined');
});
});

describe('isNonDemoOrg():', () => {
Expand All @@ -69,15 +105,54 @@ xdescribe('RefinerService', () => {
const orgName = 'Fyle for Acme Corp';
expect(refinerService.isNonDemoOrg(orgName)).toBeFalse();
});

it('should be case insensitive', () => {
const orgName = 'Fyle For Test Corp';
expect(refinerService.isNonDemoOrg(orgName)).toBeFalse();
});
});

describe('canStartSurvey():', () => {
it('should return false if eou is undefined', (done) => {
const demoOrgRes: ExtendedOrgUser = undefined;
const homeCurrency = 'INR';
const eou = demoOrgRes;

refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => {
expect(res).toBeFalse();
done();
});
});

it('should return false if ou is undefined', (done) => {
const demoOrgRes: ExtendedOrgUser = { ...apiEouRes, ou: undefined };
const homeCurrency = 'INR';
const eou = demoOrgRes;

refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => {
expect(res).toBeFalse();
done();
});
});

it('should return false if org_name is undefined', (done) => {
const demoOrgRes: ExtendedOrgUser = { ...apiEouRes, ou: { ...apiEouRes.ou, org_name: undefined } };
const homeCurrency = 'INR';
const eou = demoOrgRes;

refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => {
expect(res).toBeFalse();
done();
});
});

it('should return true for non-demo orgs and when not switched to delegator', (done) => {
spyOn(refinerService, 'isNonDemoOrg').and.returnValue(true);
const switchedToDelegator = false;
orgUserService.isSwitchedToDelegator.and.resolveTo(switchedToDelegator);
const homeCurrency = 'INR';
const eou = apiEouRes;

refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => {
expect(res).toBeTrue();
expect(orgUserService.isSwitchedToDelegator).toHaveBeenCalledTimes(1);
Expand All @@ -86,6 +161,39 @@ xdescribe('RefinerService', () => {
});
});

it('should return false for demo orgs', (done) => {
const switchedToDelegator = false;
const demoOrgRes: ExtendedOrgUser = {
...apiEouRes,
ou: {
...apiEouRes.ou,
org_name: 'Fyle for Acme Corp',
},
};
const homeCurrency = 'INR';
const eou = demoOrgRes;
spyOn(refinerService, 'isNonDemoOrg').and.returnValue(false);
orgUserService.isSwitchedToDelegator.and.resolveTo(switchedToDelegator);

refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => {
expect(res).toBeFalse();
done();
});
});

it('should return false when switched to delegator', (done) => {
const switchedToDelegator = true;
const homeCurrency = 'INR';
const eou = apiEouRes;
spyOn(refinerService, 'isNonDemoOrg').and.returnValue(true);
orgUserService.isSwitchedToDelegator.and.resolveTo(switchedToDelegator);

refinerService.canStartSurvey(homeCurrency, eou).subscribe((res) => {
expect(res).toBeFalse();
done();
});
});

it('should return false for demo orgs and when switched to delegator', (done) => {
const demoOrgRes: ExtendedOrgUser = {
...apiEouRes,
Expand All @@ -106,5 +214,18 @@ xdescribe('RefinerService', () => {
done();
});
});

it('should return false for non-demo orgs but switched to delegator', (done) => {
const eou = { ou: { org_name: 'Acme Corp' } } as ExtendedOrgUser;
spyOn(refinerService, 'isNonDemoOrg').and.returnValue(true);
orgUserService.isSwitchedToDelegator.and.resolveTo(true);

refinerService.canStartSurvey('INR', eou).subscribe((res) => {
expect(res).toBeFalse();
expect(orgUserService.isSwitchedToDelegator).toHaveBeenCalledTimes(1);
expect(refinerService.isNonDemoOrg).toHaveBeenCalledWith('Acme Corp');
done();
});
});
});
});
78 changes: 40 additions & 38 deletions src/app/core/services/refiner.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventEmitter, Injectable } from '@angular/core';
import { AuthService } from './auth.service';
import { Device } from '@capacitor/device';
import { NetworkService } from './network.service';
import { concat, forkJoin, from, Observable } from 'rxjs';
import { forkJoin, from, merge, Observable, of } from 'rxjs';
import { environment } from 'src/environments/environment';
import { ExtendedOrgUser } from '../models/extended-org-user.model';
import { map, take } from 'rxjs/operators';
Expand Down Expand Up @@ -205,14 +205,13 @@ export class RefinerService {
this.setupNetworkWatcher();
}

setupNetworkWatcher() {
const that = this;
setupNetworkWatcher(): void {
const networkWatcherEmitter = new EventEmitter<boolean>();
this.networkService.connectivityWatcher(networkWatcherEmitter);
this.isConnected$ = concat(that.networkService.isOnline(), networkWatcherEmitter.asObservable());
this.isConnected$ = merge(this.networkService.isOnline(), networkWatcherEmitter.asObservable());
}

getRegion(homeCurrency: string) {
getRegion(homeCurrency: string): string {
if (homeCurrency === 'INR') {
return 'India';
} else if (this.americasCurrencyList.includes(homeCurrency)) {
Expand All @@ -228,51 +227,54 @@ export class RefinerService {
}
}

isNonDemoOrg(orgName: string) {
return orgName.toLowerCase().indexOf('fyle for') === -1;
isNonDemoOrg(orgName: string): boolean {
return !orgName.toLowerCase().includes('fyle for');
}

canStartSurvey(homeCurrency: string, eou: ExtendedOrgUser): Observable<boolean> {
const isNonDemoOrg = eou && eou.ou && eou.ou.org_name && this.isNonDemoOrg(eou.ou.org_name);
if (!eou?.ou?.org_name) {
return of(false);
}

const isNonDemoOrg = this.isNonDemoOrg(eou.ou.org_name);
const isSwitchedToDelegator$ = from(this.orgUserService.isSwitchedToDelegator());
return isSwitchedToDelegator$.pipe(map((isSwitchedToDelegator) => isNonDemoOrg && !isSwitchedToDelegator));
}

startSurvey(properties: RefinerProperties) {
return forkJoin({
startSurvey(properties: RefinerProperties): void {
forkJoin({
isConnected: this.isConnected$.pipe(take(1)),
eou: this.authService.getEou(),
homeCurrency: this.currencyService.getHomeCurrency(),
deviceInfo: Device.getInfo(),
}).subscribe(({ isConnected, eou, homeCurrency, deviceInfo }) => {
// if (this.canStartSurvey(homeCurrency, eou) && isConnected) {
// let device = '';
// if (deviceInfo.operatingSystem === 'ios') {
// device = 'IOS';
// } else if (deviceInfo.operatingSystem === 'android') {
// device = 'ANDROID';
// }
// (window as typeof window & { _refiner: (eventName: string, payload: IdentifyUserPayload) => void })._refiner(
// 'identifyUser',
// {
// id: eou.us.id, // Replace with your user ID
// email: eou.us.email, // Replace with user Email
// name: eou.us.full_name, // Replace with user name
// account: {
// company_id: eou.ou.org_id,
// company_name: eou.ou.org_name,
// region: this.getRegion(homeCurrency) + ' - ' + homeCurrency,
// },
// source: 'Mobile' + ' - ' + device,
// is_admin: eou && eou.ou && eou.ou.roles && eou.ou.roles.indexOf('ADMIN') > -1 ? 'T' : 'F',
// action_name: properties.actionName,
// }
// );
// (window as typeof window & { _refiner: (eventName: string, payload: string) => void })._refiner(
// 'showForm',
// environment.REFINER_NPS_FORM_ID
// );
// }
if (this.canStartSurvey(homeCurrency, eou) && isConnected) {
const device = deviceInfo.operatingSystem.toUpperCase();
(window as typeof window & { _refiner: (eventName: string, payload: IdentifyUserPayload) => void })._refiner(
'identifyUser',
{
id: eou.us.id, // Replace with your user ID
email: eou.us.email, // Replace with user Email
name: eou.us.full_name, // Replace with user name
account: {
company_id: eou.ou.org_id,
company_name: eou.ou.org_name,
region: `${this.getRegion(homeCurrency)} - ${homeCurrency}`,
},
source: `Mobile - ${device}`,
is_admin: eou?.ou?.roles?.includes('ADMIN') ? 'T' : 'F',
action_name: properties.actionName,
}
);
(window as typeof window & { _refiner: (eventName: string, payload: string) => void })._refiner(
'setProject',
environment.REFINER_NPS_PROJECT_ID
);
(window as typeof window & { _refiner: (eventName: string, payload: string) => void })._refiner(
'showForm',
environment.REFINER_NPS_FORM_ID
);
}
});
}
}
4 changes: 2 additions & 2 deletions src/app/fyle/add-edit-expense/add-edit-expense-1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,8 @@ export function TestCases1(getTestBed) {
networkService.isOnline.and.returnValue(of(true));

component.setupNetworkWatcher();
expect(networkService.connectivityWatcher).toHaveBeenCalledOnceWith(new EventEmitter<boolean>());
expect(networkService.isOnline).toHaveBeenCalledTimes(1);
expect(networkService.connectivityWatcher).toHaveBeenCalledWith(new EventEmitter<boolean>());
expect(networkService.isOnline).toHaveBeenCalled();
component.isConnected$.subscribe((res) => {
expect(res).toBeTrue();
done();
Expand Down
18 changes: 18 additions & 0 deletions src/app/fyle/add-edit-expense/add-edit-expense-2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,24 @@ export function TestCases2(getTestBed) {
done();
});
});

it('should update txn date with invoice_dt', (done) => {
const mockedTxn = cloneDeep(transformedExpenseWithExtractedData2);
const extractedDate = new Date('2023-01-24');

mockedTxn.tx.txn_dt = new Date('2023-01-23');
mockedTxn.tx.extracted_data.invoice_dt = new Date('2023-01-23');
mockedTxn.tx.extracted_data.date = extractedDate;
expensesService.getExpenseById.and.returnValue(of(platformExpenseWithExtractedData2));
transactionService.transformExpense.and.returnValue(mockedTxn);
dateService.getUTCDate.and.returnValue(mockedTxn.tx.extracted_data.invoice_dt);

component.getEditExpenseObservable().subscribe((res) => {
expect(res).toEqual(mockedTxn);
expect(mockedTxn.tx.txn_dt).toEqual(mockedTxn.tx.extracted_data.invoice_dt);
done();
});
});
});

it('goToPrev(): should go to the previous txn', () => {
Expand Down
28 changes: 28 additions & 0 deletions src/app/fyle/add-edit-expense/add-edit-expense-3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,34 @@ export function TestCases3(getTestBed) {
},
});
});

it('should set default comment if user wants to continue with violations but does not provide a comment', (done) => {
loaderService.hideLoader.and.resolveTo();
loaderService.showLoader.and.resolveTo();
component.etxn$ = of(unflattenedTxnData2);
spyOn(component, 'continueWithPolicyViolations').and.resolveTo({ comment: '' });
spyOn(component, 'generateEtxnFromFg').and.returnValue(of(unflattenedExpData));

component
.policyViolationErrorHandler(
{
policyViolations: criticalPolicyViolation1,
policyAction: policyViolation1.data.final_desired_state,
},
of(customFieldData2)
)
.subscribe((result) => {
expect(loaderService.hideLoader).toHaveBeenCalledTimes(1);
expect(loaderService.showLoader).toHaveBeenCalledTimes(1);
expect(component.continueWithPolicyViolations).toHaveBeenCalledOnceWith(
criticalPolicyViolation1,
policyViolation1.data.final_desired_state
);
expect(component.generateEtxnFromFg).toHaveBeenCalledTimes(1);
expect(result.comment).toBe('No policy violation explanation provided');
done();
});
});
});

describe('viewAttachments():', () => {
Expand Down
Loading

0 comments on commit bf4b3df

Please sign in to comment.