Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New polling logic for DE #3229

Merged
merged 7 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 114 additions & 2 deletions src/app/fyle/my-expenses/my-expenses.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute, NavigationStart, Params, Router } from '@angular/router';
import { ActionSheetController, ModalController, NavController, PopoverController } from '@ionic/angular';
import { cloneDeep, isEqual } from 'lodash';
import { cloneDeep, isEqual, isNumber } from 'lodash';
import {
BehaviorSubject,
Observable,
Expand All @@ -17,17 +17,22 @@
iif,
noop,
of,
timer,
} from 'rxjs';
import {
debounceTime,
distinctUntilChanged,
exhaustMap,
filter,
finalize,
map,
shareReplay,
startWith,
switchMap,
take,
takeUntil,
takeWhile,
timeout,
} from 'rxjs/operators';
import { BackButtonActionPriority } from 'src/app/core/models/back-button-action-priority.enum';
import { Expense } from 'src/app/core/models/expense.model';
Expand Down Expand Up @@ -78,6 +83,8 @@
import { AuthService } from 'src/app/core/services/auth.service';
import { UtilityService } from 'src/app/core/services/utility.service';
import { FeatureConfigService } from 'src/app/core/services/platform/v1/spender/feature-config.service';
import * as dayjs from 'dayjs';
import { ExpensesQueryParams } from 'src/app/core/models/platform/v1/expenses-query-params.model';

@Component({
selector: 'app-my-expenses',
Expand Down Expand Up @@ -445,6 +452,97 @@
}
}

private isZeroAmountPerDiemOrMileage(expense: PlatformExpense): boolean {
return (
(expense?.category?.name?.toLowerCase() === 'per diem' || expense?.category?.name?.toLowerCase() === 'mileage') &&
(expense.amount === 0 || expense.claim_amount === 0)
);
}

/**
* Checks if the scan process for an expense has been completed.
* @param {PlatformExpense} expense - The expense to check.

Check failure on line 464 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 464 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @returns {boolean} - True if the scan is complete or if data is manually entered.

Check failure on line 465 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @returns

Check failure on line 465 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @returns
*/
private isExpenseScanComplete(expense: PlatformExpense): boolean {
const isZeroAmountPerDiemOrMileage = this.isZeroAmountPerDiemOrMileage(expense);

const hasUserManuallyEnteredData =
isZeroAmountPerDiemOrMileage ||
((expense.amount || expense.claim_amount) && isNumber(expense.amount || expense.claim_amount));
const isDataExtracted = !!expense.extracted_data;

// this is to prevent the scan failed from being shown from an indefinite amount of time.
const hasScanExpired = expense.created_at && dayjs(expense.created_at).diff(Date.now(), 'day') < 0;
return !!(hasUserManuallyEnteredData || isDataExtracted || hasScanExpired);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken from expenses card component


/**
* Filters the list of expenses to get only those with incomplete scans.
* @param {PlatformExpense[]} expenses - The list of expenses to check.

Check failure on line 482 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 482 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @returns {string[]} - Array of expense IDs that have incomplete scans.

Check failure on line 483 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @returns

Check failure on line 483 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @returns
*/
private filterIncompleteExpenses(expenses: PlatformExpense[]): string[] {
return expenses.filter((expense) => !this.isExpenseScanComplete(expense)).map((expense) => expense.id);
}

/**
* Updates the expenses with polling results.
* @param {PlatformExpense[]} initialExpenses - The initial list of expenses.

Check failure on line 491 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 491 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @param {PlatformExpense[]} updatedExpenses - The updated expenses after polling.

Check failure on line 492 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 492 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @param {string[]} incompleteExpenseIds - Array of expense IDs with incomplete scans.

Check failure on line 493 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 493 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @returns {PlatformExpense[]} - Updated list of expenses.

Check failure on line 494 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @returns

Check failure on line 494 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @returns
*/
private updateExpensesList(
initialExpenses: PlatformExpense[],
updatedExpenses: PlatformExpense[],
incompleteExpenseIds: string[]
): PlatformExpense[] {
const updatedExpensesMap = new Map(updatedExpenses.map((expense) => [expense.id, expense]));

const newExpensesList = initialExpenses.map((expense) => {
if (incompleteExpenseIds.includes(expense.id)) {
const updatedExpense = updatedExpensesMap.get(expense.id);
if (this.isExpenseScanComplete(updatedExpense)) {
return updatedExpense;
}
}
return expense;
});

return newExpensesList;
}

/**
* Polls for expenses that have incomplete scan data.
* @param {string[]} incompleteExpenseIds - Array of expense IDs with incomplete scans.

Check failure on line 518 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 518 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @param {PlatformExpense[]} initialExpenses - The initial list of expenses.

Check failure on line 519 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param

Check failure on line 519 in src/app/fyle/my-expenses/my-expenses.page.ts

View workflow job for this annotation

GitHub Actions / Run linters

Types are not permitted on @param
* @returns {Observable<PlatformExpense[]>} - Observable that emits updated expenses.
*/
private pollIncompleteExpenses(
incompleteExpenseIds: string[],
expenses: PlatformExpense[]
): Observable<PlatformExpense[]> {
let updatedExpensesList = expenses;
// Create a stop signal that emits after 30 seconds
const stopPolling$ = timer(30000);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taking 30 seconds as the limit for polling after every 5 seconds.

return timer(5000, 5000).pipe(
exhaustMap(() => {
const params: ExpensesQueryParams = { queryParams: { id: `in.(${incompleteExpenseIds.join(',')})` } };
return this.expenseService.getExpenses({ ...params.queryParams }).pipe(
map((updatedExpenses) => {
updatedExpensesList = this.updateExpensesList(updatedExpensesList, updatedExpenses, incompleteExpenseIds);
incompleteExpenseIds = this.filterIncompleteExpenses(updatedExpenses);
return updatedExpensesList;
})
);
}),
takeWhile(() => incompleteExpenseIds.length > 0, true),
takeUntil(stopPolling$),
takeUntil(this.onPageExit$)
);
}

ionViewWillEnter(): void {
this.isNewReportsFlowEnabled = false;
this.hardwareBackButton = this.platformHandlerService.registerBackButtonAction(
Expand Down Expand Up @@ -612,7 +710,21 @@
})
);

this.myExpenses$ = paginatedPipe.pipe(shareReplay(1));
/**
* Observable that manages expenses, including polling for incomplete scans.
*/
this.myExpenses$ = paginatedPipe.pipe(
switchMap((expenses) => {
const incompleteExpenseIds = this.filterIncompleteExpenses(expenses);

if (incompleteExpenseIds.length === 0) {
return of(expenses); // All scans are completed
} else {
return this.pollIncompleteExpenses(incompleteExpenseIds, expenses).pipe(startWith(expenses), timeout(30000));
}
}),
shareReplay(1)
);

this.count$ = this.loadExpenses$.pipe(
switchMap((params) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export class ExpensesCardComponent implements OnInit {
(that.homeCurrency === 'USD' || that.homeCurrency === 'INR')
) {
that.isScanCompleted = that.checkIfScanIsCompleted();
that.isScanInProgress = !that.isScanCompleted;
that.isScanInProgress = !that.isScanCompleted && !this.expense.extracted_data;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if isScanInProgress is false and isScanCompleted is also false, then the scan failed message is shown. This is the current behaviour.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be that.expense.extracted_data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, If extracted_data is present, then it means that the scan is completed.

} else {
that.isScanCompleted = true;
that.isScanInProgress = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export class ExpensesCardComponent implements OnInit {
(that.homeCurrency === 'USD' || that.homeCurrency === 'INR')
) {
that.isScanCompleted = that.checkIfScanIsCompleted();
that.isScanInProgress = !that.isScanCompleted;
that.isScanInProgress = !that.isScanCompleted && !this.expense.tx_extracted_data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above.

} else {
that.isScanCompleted = true;
that.isScanInProgress = false;
Expand Down
Loading