Skip to content

Commit

Permalink
feat: add pagination support to load cases (#1303)
Browse files Browse the repository at this point in the history
- Refactor `loadCasesCached` to `loadCasesCachedWithPagination` for enhanced pagination support.
- Implement `loadCasesPaginated` function to handle API requests with pagination.
- Update method invocations in PhoneSystem.vue, Work.vue, and dashboard.ts to use the new `loadCasesCachedWithPagination`.
- Improve cache reconciliation logic to ensure data consistency.
  • Loading branch information
tabiodun authored Nov 21, 2024
1 parent f3cc2d8 commit ab1aa47
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 8 deletions.
6 changes: 3 additions & 3 deletions src/pages/Work.vue
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ import Worksite from '../models/Worksite';
import CaseHistory from '../components/work/CaseHistory.vue';
import {
loadCaseImagesCached,
loadCasesCached,
loadCasesCachedWithPagination,
loadUserLocations,
} from '../utils/worksite';
import { averageGeolocation, getUserLocationLayer } from '../utils/map';
Expand Down Expand Up @@ -1256,7 +1256,7 @@ export default defineComponent({
async function getWorksites() {
mapLoading.value = true;
const response = await loadCasesCached({
const response = await loadCasesCachedWithPagination({
...worksiteQuery.value,
});
mapLoading.value = false;
Expand All @@ -1274,7 +1274,7 @@ export default defineComponent({
async function getAllWorksites() {
mapLoading.value = true;
const response = await loadCasesCached({
const response = await loadCasesCachedWithPagination({
incident: currentIncidentId.value,
});
mapLoading.value = false;
Expand Down
4 changes: 2 additions & 2 deletions src/pages/phone/PhoneSystem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ import useDialogs from '../../hooks/useDialogs';
import useConnectFirst from '../../hooks/useConnectFirst';
import User from '../../models/User';
import WorksiteForm from '../../components/work/WorksiteForm.vue';
import { loadCasesCached } from '@/utils/worksite';
import { loadCasesCachedWithPagination } from '@/utils/worksite';
import { getErrorMessage } from '@/utils/errors';
import usePhoneService from '@/hooks/phone/usePhoneService';
import WorksiteTable from '@/components/work/WorksiteTable.vue';
Expand Down Expand Up @@ -1120,7 +1120,7 @@ export default defineComponent({
async function getWorksites() {
mapLoading.value = true;
const response = await loadCasesCached({
const response = await loadCasesCachedWithPagination({
incident: currentIncidentId.value,
});
mapLoading.value = false;
Expand Down
4 changes: 2 additions & 2 deletions src/utils/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getApiUrl } from '@/utils/helpers';
import Worksite from '@/models/Worksite';
import { getQueryString } from '@/utils/urls';
import InvitationRequest from '@/models/InvitationRequest';
import { loadCasesCached } from '@/utils/worksite';
import { loadCasesCachedWithPagination } from '@/utils/worksite';
import User from '@/models/User';
import Organization from '@/models/Organization';

Expand Down Expand Up @@ -73,7 +73,7 @@ async function getInvitationRequests() {
}

async function getWorksites(incident) {
const response = await loadCasesCached({
const response = await loadCasesCachedWithPagination({
incident: incident,
});
return response.results;
Expand Down
123 changes: 122 additions & 1 deletion src/utils/worksite.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import moment from 'moment';
import type { AxiosResponse } from 'axios';
import axios from 'axios';
import { DbService, WORKSITE_IMAGES_DATABASE } from '@/services/db.service';
import { generateHash } from './helpers';
Expand Down Expand Up @@ -26,6 +27,36 @@ const loadCases = async (query: Record<string, unknown>) => {
return response.data;
};

const loadCasesPaginated = async (query: Record<string, unknown>) => {
const results: CachedCase[] = [];
let nextUrl: string | null =
`${import.meta.env.VITE_APP_API_BASE_URL}/worksites_page`;
let params = { ...query };

while (nextUrl) {
const response: AxiosResponse = await axios.get<CachedCaseResponse>(
nextUrl,
{ params },
);
const { data } = response;
results.push(...data.results);
nextUrl = data.next;

// If nextUrl is relative, construct the full URL
if (nextUrl && !nextUrl.startsWith('http')) {
nextUrl = `${import.meta.env.VITE_APP_API_BASE_URL}${nextUrl}`;
}

// After the first request, params are included in the next URL
params = {};
}

return {
count: results.length,
results,
};
};

const loadUserLocations = async (query: Record<string, unknown>) => {
const response = await axios.get(
`${import.meta.env.VITE_APP_API_BASE_URL}/user_geo_locations/latest_locations`,
Expand Down Expand Up @@ -112,6 +143,90 @@ const loadCasesCached = async (query: Record<string, unknown>) => {
return results;
};

const loadCasesCachedWithPagination = async (
query: Record<string, unknown>,
) => {
const queryKeys = Object.keys(query).sort();
const sortedQuery: Record<string, unknown> = {};
for (const key of queryKeys) {
sortedQuery[key] = query[key];
}

query = {
...query,
limit: 5000,
};

const queryHash = generateHash(JSON.stringify(sortedQuery));
const cacheKeys = {
CASES: `cachedCases:${queryHash}`,
UPDATED: `casesUpdated:${queryHash}`,
RECONCILED: `casesReconciled:${queryHash}`,
};
debug('loadCasesCached::QueryHash %o | %s', query, queryHash);
const cachedCases = (await DbService.getItem(
cacheKeys.CASES,
)) as CachedCaseResponse;
const casesUpdatedAt = (await DbService.getItem(cacheKeys.UPDATED)) as string; // ISO date string
const casesReconciledAt = ((await DbService.getItem(cacheKeys.RECONCILED)) ||
moment().toISOString()) as string; // ISO date string

if (cachedCases) {
const [response, reconciliationResponse] = await Promise.all([
loadCasesPaginated({
...query,
updated_at__gt: casesUpdatedAt,
}),
loadCasesPaginated({
updated_at__gt: casesReconciledAt,
fields: 'id,incident',
}),
]);

// Reconcile data
for (const element of reconciliationResponse.results) {
const itemIndex = cachedCases.results.findIndex(
(o) => o.id === element.id,
);
if (
itemIndex > -1 &&
element.incident !== cachedCases.results[itemIndex].incident
) {
cachedCases.results.splice(itemIndex, 1);
}
}

await DbService.setItem(cacheKeys.RECONCILED, moment().toISOString());
await DbService.setItem(cacheKeys.CASES, cachedCases);

if (response.results.length === 0) {
return cachedCases;
}

for (const element of response.results) {
const itemIndex = cachedCases.results.findIndex(
(o) => o.id === element.id,
);
if (itemIndex > -1) {
cachedCases.results[itemIndex] = element;
} else {
cachedCases.results.push(element);
}
}

cachedCases.count = cachedCases.results.length;

await DbService.setItem(cacheKeys.CASES, cachedCases);
await DbService.setItem(cacheKeys.UPDATED, moment().toISOString());
return cachedCases;
}

const results = await loadCasesPaginated(query);
await DbService.setItem(cacheKeys.CASES, results);
await DbService.setItem(cacheKeys.UPDATED, moment().toISOString());
return results;
};

const loadCaseImagesCached = async (query) => {
// Sort the query keys
const queryKeys = Object.keys(query).sort();
Expand Down Expand Up @@ -324,4 +439,10 @@ const loadCaseImagesCached = async (query) => {
return finalData;
};

export { loadCasesCached, loadCases, loadCaseImagesCached, loadUserLocations };
export {
loadCasesCached,
loadCasesCachedWithPagination,
loadCases,
loadCaseImagesCached,
loadUserLocations,
};

0 comments on commit ab1aa47

Please sign in to comment.