Skip to content

Commit

Permalink
Merge pull request #757 from yeatmanlab/ref/318/query-composables-cre…
Browse files Browse the repository at this point in the history
…ate-administration

Migrate Create Administration page to composable TanStack queries
  • Loading branch information
maximilianoertel authored Oct 1, 2024
2 parents 525349e + a5bcabf commit b00f802
Show file tree
Hide file tree
Showing 40 changed files with 932 additions and 598 deletions.
504 changes: 173 additions & 331 deletions src/components/CreateAdministration.vue

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/components/TaskPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,14 @@ const taskOptions = computed(() => {
watch(
() => props.inputVariants,
(newVariants) => {
// @TODO: Fix this as it's not working as expected. When updating the data set in the parent component, the data is
// added twice to the selectedVariants array, despite the _union call.
selectedVariants.value = _union(selectedVariants.value, newVariants);
// Update the conditions for the variants that were pre-existing
selectedVariants.value = selectedVariants.value.map((variant) => {
const preExistingInfo = props.preExistingAssessmentInfo.find((info) => info?.variantId === variant?.id);
if (preExistingInfo) {
return { ...variant, variant: { ...variant?.variant, conditions: preExistingInfo.conditions } };
}
Expand All @@ -205,7 +208,6 @@ const updateVariant = (variantId, conditions) => {
});
selectedVariants.value = updatedVariants;
return;
// props.selectedVariant[]
};
const selectedVariants = ref([]);
Expand Down
40 changes: 40 additions & 0 deletions src/composables/mutations/useUpsertAdministrationMutation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useMutation, useQueryClient } from '@tanstack/vue-query';
import { useAuthStore } from '@/store/auth';
import { ADMINISTRATION_UPSERT_MUTATION_KEY } from '@/constants/mutationKeys';
import {
ADMINISTRATIONS_QUERY_KEY,
ADMINISTRATIONS_LIST_QUERY_KEY,
ADMINISTRATION_ASSIGNMENTS_QUERY_KEY,
} from '@/constants/queryKeys';

/**
* Upsert Administration mutation.
*
* TanStack mutation to update or insert an administration and automatically invalidate the corresponding queries.
*
* @returns {Object} The mutation object returned by `useMutation`.
*/
const useUpsertAdministrationMutation = () => {
const authStore = useAuthStore();
const queryClient = useQueryClient();

return useMutation({
mutationKey: ADMINISTRATION_UPSERT_MUTATION_KEY,
mutationFn: async (data) => {
await authStore.roarfirekit.createAdministration(data);
},
onSuccess: () => {
// Invalidate the queries to refetch the administration data.
// @NOTE: Usually we would apply a more granular invalidation strategy including updating the specific
// adminitration record in the cache. However, unfortunately, given the nature of the data model and the data that
// is updated in the application, we would have to manually map the updated data, which could cause issues when
// the data model changes. Therefore, we invalidate the entire query to ensure the data is up-to-date.
queryClient.invalidateQueries({ queryKey: [ADMINISTRATIONS_QUERY_KEY] });
queryClient.invalidateQueries({ queryKey: [ADMINISTRATIONS_LIST_QUERY_KEY] });
queryClient.invalidateQueries({ queryKey: [ADMINISTRATION_ASSIGNMENTS_QUERY_KEY] });
console.warn('Administration upserted successfully.');
},
});
};

export default useUpsertAdministrationMutation;
25 changes: 11 additions & 14 deletions src/composables/queries/useAdministrationsQuery.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { fetchDocsById } from '@/helpers/query/utils';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocumentsById } from '@/helpers/query/utils';
import { ADMINISTRATIONS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';

Expand All @@ -12,19 +13,15 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
* @returns {UseQueryResult} The TanStack query result.
*/
const useAdministrationsQuery = (administrationIds, queryOptions = undefined) => {
// Ensure all necessary data is available before enabling the query.
const conditions = [() => hasArrayEntries(administrationIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);

return useQuery({
queryKey: [ADMINISTRATIONS_QUERY_KEY, toValue(administrationIds)],
queryFn: () =>
fetchDocsById(
toValue(administrationIds)?.map((administrationId) => {
return {
collection: FIRESTORE_COLLECTIONS.ADMINISTRATIONS,
docId: administrationId,
select: ['name', 'publicName', 'sequential', 'assessments', 'legal'],
};
}),
),
...queryOptions,
queryKey: [ADMINISTRATIONS_QUERY_KEY, administrationIds],
queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.ADMINISTRATIONS, administrationIds),
enabled: isQueryEnabled,
...options,
});
};

Expand Down
78 changes: 61 additions & 17 deletions src/composables/queries/useAdministrationsQuery.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ref, toValue } from 'vue';
import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
import { withSetup } from '@/test-support/withSetup.js';
import { fetchDocsById } from '@/helpers/query/utils';
import { fetchDocumentsById } from '@/helpers/query/utils';
import useAdministrationsQuery from './useAdministrationsQuery';

vi.mock('@/helpers/query/utils', () => ({
fetchDocsById: vi.fn().mockImplementation(() => []),
fetchDocumentsById: vi.fn().mockImplementation(() => []),
}));

vi.mock('@tanstack/vue-query', async (getModule) => {
Expand All @@ -18,14 +18,6 @@ vi.mock('@tanstack/vue-query', async (getModule) => {
};
});

const buildCollectionRequestPayload = (id) => {
return {
collection: 'administrations',
docId: id,
select: ['name', 'publicName', 'sequential', 'assessments', 'legal'],
};
};

describe('useAdministrationsQuery', () => {
let queryClient;

Expand All @@ -46,13 +38,14 @@ describe('useAdministrationsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations', toValue(mockAdministrationIds)],
queryKey: ['administrations', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});

const expectedPayload = mockAdministrationIds.value.map((id) => buildCollectionRequestPayload(id));

expect(fetchDocsById).toHaveBeenCalledWith(expectedPayload);
expect(fetchDocumentsById).toHaveBeenCalledWith('administrations', mockAdministrationIds);
});

it('should allow the query to be disabled via the passed query options', () => {
Expand All @@ -66,9 +59,60 @@ describe('useAdministrationsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations', toValue(mockAdministrationIds)],
queryKey: ['administrations', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
}),
});

expect(fetchDocumentsById).not.toHaveBeenCalled();
});

it('should only fetch data if the administration IDs are available', async () => {
const mockAdministrationIds = ref(null);
const queryOptions = { enabled: true };

vi.spyOn(VueQuery, 'useQuery');

withSetup(() => useAdministrationsQuery(mockAdministrationIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: false,
enabled: expect.objectContaining({
_value: false,
}),
});

expect(fetchDocumentsById).not.toHaveBeenCalled();

mockAdministrationIds.value = [nanoid(), nanoid()];
await nextTick();

expect(fetchDocumentsById).toHaveBeenCalledWith('administrations', mockAdministrationIds);
});

it('should not let queryOptions override the internally computed value', async () => {
const mockAdministrationIds = ref(null);
const queryOptions = { enabled: true };

vi.spyOn(VueQuery, 'useQuery');

withSetup(() => useAdministrationsQuery(mockAdministrationIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
}),
});

expect(fetchDocumentsById).not.toHaveBeenCalled();
});
});
11 changes: 9 additions & 2 deletions src/composables/queries/useAdministrationsStatsQuery.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocsById } from '@/helpers/query/utils';
import { ADMINISTRATIONS_STATS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
Expand All @@ -12,8 +14,12 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
* @returns {UseQueryResult} The TanStack query result.
*/
const useAdministrationsStatsQuery = (administrationIds, queryOptions = undefined) => {
// Ensure all necessary data is available before enabling the query.
const conditions = [() => hasArrayEntries(administrationIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);

return useQuery({
queryKey: [ADMINISTRATIONS_STATS_QUERY_KEY, toValue(administrationIds)],
queryKey: [ADMINISTRATIONS_STATS_QUERY_KEY, administrationIds],
queryFn: () =>
fetchDocsById(
toValue(administrationIds)?.map((administrationId) => {
Expand All @@ -23,7 +29,8 @@ const useAdministrationsStatsQuery = (administrationIds, queryOptions = undefine
};
}),
),
...queryOptions,
enabled: isQueryEnabled,
...options,
});
};

Expand Down
64 changes: 60 additions & 4 deletions src/composables/queries/useAdministrationsStatsQuery.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ref, toValue } from 'vue';
import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
Expand Down Expand Up @@ -45,8 +45,11 @@ describe('useAdministrationsStatsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations-stats', toValue(mockAdministrationIds)],
queryKey: ['administrations-stats', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});

const expectedPayload = mockAdministrationIds.value.map((id) => buildCollectionRequestPayload(id));
Expand All @@ -65,9 +68,62 @@ describe('useAdministrationsStatsQuery', () => {
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations-stats', toValue(mockAdministrationIds)],
queryKey: ['administrations-stats', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: false,
enabled: expect.objectContaining({
_value: false,
}),
});

expect(fetchDocsById).not.toHaveBeenCalled();
});

it('should only fetch data if the administration IDs are available', async () => {
const mockAdministrationIds = ref(null);
const queryOptions = { enabled: true };

vi.spyOn(VueQuery, 'useQuery');

withSetup(() => useAdministrationsStatsQuery(mockAdministrationIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations-stats', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
}),
});

expect(fetchDocsById).not.toHaveBeenCalled();

mockAdministrationIds.value = [nanoid(), nanoid()];
await nextTick();

const expectedPayload = mockAdministrationIds.value.map((id) => buildCollectionRequestPayload(id));

expect(fetchDocsById).toHaveBeenCalledWith(expectedPayload);
});

it('should not let queryOptions override the internally computed value', async () => {
const mockAdministrationIds = ref(null);
const queryOptions = { enabled: true };

vi.spyOn(VueQuery, 'useQuery');

withSetup(() => useAdministrationsStatsQuery(mockAdministrationIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});

expect(VueQuery.useQuery).toHaveBeenCalledWith({
queryKey: ['administrations-stats', mockAdministrationIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
}),
});

expect(fetchDocsById).not.toHaveBeenCalled();
});
});
7 changes: 3 additions & 4 deletions src/composables/queries/useClassesQuery.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import _isEmpty from 'lodash/isEmpty';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
import { fetchDocumentsById } from '@/helpers/query/utils';
import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { CLASSES_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';

Expand All @@ -15,12 +14,12 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
*/
const useClassesQuery = (classIds, queryOptions = undefined) => {
// Ensure all necessary data is loaded before enabling the query.
const conditions = [() => !_isEmpty(classIds)];
const conditions = [() => hasArrayEntries(classIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);

return useQuery({
queryKey: [CLASSES_QUERY_KEY, classIds],
queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.CLASSES, toValue(classIds)),
queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.CLASSES, classIds),
enabled: isQueryEnabled,
...options,
});
Expand Down
10 changes: 5 additions & 5 deletions src/composables/queries/useDistrictsListQuery.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ describe('useDistrictsListQuery', () => {
});

it('should only fetch districts only once user claims are loaded', async () => {
const mockData = ref({});
const mockIsLoading = ref(true);
const mockClaimsData = ref({});
const mockClaimsLoading = ref(true);

vi.mocked(useUserClaimsQuery).mockReturnValue({ data: mockData, isLoading: mockIsLoading });
vi.mocked(useUserClaimsQuery).mockReturnValue({ data: mockClaimsData, isLoading: mockClaimsLoading });

vi.spyOn(VueQuery, 'useQuery');
vi.spyOn(orgFetcher, 'mockImplementation');
Expand All @@ -93,8 +93,8 @@ describe('useDistrictsListQuery', () => {

expect(orgFetcher).not.toHaveBeenCalled();

mockData.value = mockSuperAdminUserClaims.value;
mockIsLoading.value = false;
mockClaimsData.value = mockSuperAdminUserClaims.value;
mockClaimsLoading.value = false;

await nextTick();

Expand Down
Loading

0 comments on commit b00f802

Please sign in to comment.